The micropayment revolution is underway
Payment is just another layer.
x402 is an open protocol that lets an HTTP request pay for itself — a few cents of USDC, settled in seconds, no account on either side. Crosshatch brings it to Effect: pay as a layer, charge as a route.
$ pnpm add crosshatch
x402 v2 TypeScript Apache-2.0
Your program sees one request that succeeds — Crosshatch handles the middle. Step through it →
New to x402?
One paid request, step by step
The whole protocol is those four wire moves. Click through what each one carries — real x402 v2 headers, decoded.
Step 1 · Client — your code
An ordinary HTTP call. No account, no key — the client knows nothing about this API yet.
POST /v1/reports HTTP/1.1
host: api.example.com
accept: application/json
content-type: application/json
{ "topic": "eurusd-volatility" }
// no auth header, no api key —
// the server will answer with a price.
you write: client.execute(HttpClientRequest.post("/v1/reports"))
Step 2 · Merchant — Http402.require
Instead of a body, the response carries terms — what this costs and how to pay.
Decoded from the payment-required header:
{
"x402Version": 2,
"resource": { "description": "One market report" },
"accepts": [{
"scheme": "exact",
"network": "eip155:8453",
"asset": "0x8335…2913",
"amount": "10000", // $0.01 — USDC has 6 decimals
"payTo": "0x1c7d…9a42",
"maxTimeoutSeconds": 300
}]
}
the merchant wrote: yield* Http402.require({ required })
Step 3 · Client — Http402.layerClient
Crosshatch picks an option it can satisfy, signs a transfer authorization, and retries —
your program is still awaiting its first response.
Decoded from the payment-signature header:
{
"x402Version": 2,
"accepted": { "scheme": "exact", "network": "eip155:8453", … },
"payload": {
"signature": "0x74f9…01b3",
"authorization": {
"from": "0x9e04…77c1", // client wallet → merchant
"to": "0x1c7d…9a42",
"value": "10000", "nonce": "0x3fb2…c4d8"
}
}
}
handled by Http402.layerClient + your Payer
Step 4 · Merchant — Facilitator.settle
A facilitator verifies and settles — money moves now, not at month-end.
The response is the resource, plus a receipt in payment-response:
{
"success": true,
"transaction": "0xa9c1…e77b", // settlement receipt
"network": "eip155:8453",
"payer": "0x9e04…77c1"
}
// …plus the body your program asked for:
{ "report": "…" }
the merchant wrote: yield* Facilitator.settle({ payload })
Merchant · charge
From free to paid in one flip
402 Payment Required has been in the HTTP spec since 1997, waiting for money that moves at the speed of a request. USDC ended the wait — settlement in seconds, fees in fractions of a cent — just as software learned to spend money of its own. Here’s an ordinary Effect route: flip the switch.
+import { Facilitator, Http402, KnownAssets, Payload, Required, Requirements } from "crosshatch" import { Effect } from "effect"
import { HttpServerResponse } from "effect/unstable/http"
export const route = Effect.gen(function* () {+ const payload = yield* Payload.Payload+ if (!payload) {+ const required = yield* Required.make`One market report`.pipe(+ Required.accept(Requirements.logical(KnownAssets.Usd.USDC, {+ amount: 0.01,+ recipients: { eip155: { 8453: payTo } },+ })),+ )+ return yield* Http402.require({ required })+ }+ const settlement = yield* Facilitator.settle({ payload }) const report = yield* Reports.generate()
return HttpServerResponse.json(report)+ .pipe(Http402.addResponseHeader(settlement)) })
The green lines are the entire diff. Merchant quickstart →
- API keys
- plan tiers
- usage metering
- invoices
- failed-payment emails
- webhook reconciliation
What’s left is a price on the route.
Client · pay
Paying is one layer
Provide Http402.layerClient and 402s are paid and retried inside the client.
Highlighted lines are Crosshatch; the rest is ordinary Effect.
An LLM call, bought with USDC instead of an API key
No signup anywhere in this program — exactly what an agent needs to buy data, tools, or model calls mid-task. The same composition works for any paid HTTP resource.
Client quickstart →import { OpenAiClient, OpenAiLanguageModel } from "@effect/ai-openai-compat"
import { Http402 } from "crosshatch"
import { Effect, Layer } from "effect"
import { LanguageModel } from "effect/unstable/ai"
import { PayerLive } from "./payer" // wallet + signing (see quickstart)
// Any x402-priced API. No account. No key.
const Blockrun = OpenAiLanguageModel.layer({
model: "deepseek/deepseek-chat",
}).pipe(
Layer.provide(OpenAiClient.layer({ apiUrl: "https://blockrun.ai/api/v1" })),
)
LanguageModel.generateText({
prompt: "Hello from Crosshatch.",
}).pipe(
Effect.provide(Blockrun.pipe(
Layer.provide(Http402.layerClient.pipe(Layer.provide(PayerLive))),
)),
Effect.runFork,
)
Why Crosshatch
The protocol is simple. The details aren’t.
The demos are short because the machinery lives down here — x402 v2, end to end.
Requirements.logical(USDC, { amount: 0.01 })
- token addresses
- decimals
- signing domains
for every supported network — EVM and Solana alike.
yield* Payload.Payload
- header decoding
- schema validation
- typed failures
every x402 v2 message and CAIP identifier — malformed payments die at the boundary.
Http402.require({ required })
- base64 headers
- response shapes
- versioning
the v2 wire format, down to the 402 itself.
Facilitator.settle({ payload })
- signature checks
- replay protection
- on-chain settlement
through a hosted facilitator, or your own — same toolkit.
Http402.layerClient
- 402 interception
- option selection
- signing
- retry
one Layer — typed errors, tracing, and structured concurrency throughout.
crosshatch profile add
- key generation
- keychain encryption
- funding
a dev wallet in one command.
And it meets Effect where it already is: HttpClient, server middleware, Effect RPC — streaming payments per message — Effect AI, and effect-atom.
FAQ
Is this crypto? Do I need to care about blockchains?
There’s no token and nothing to speculate on. x402 settles in stablecoins like USDC —
tokens that hold a steady $1 — the only rail that can move half a cent, machine to machine,
in seconds. You price in dollars; for most apps the entire crypto surface is
KnownAssets.Usd.USDC and an amount.
Why not just use Stripe?
Different regime. Card rails cost ~30¢ before your price starts, and every buyer must be a human with a card and a signup. x402 is for the other case: prices in cents or fractions of one, and buyers that are often programs. Selling $29/month to people? Keep Stripe. Selling $0.01 requests to anyone — or anything? That’s x402.
Doesn’t paying per request make everything slow?
The 402 exchange adds one round trip before the first paid response. Verifying a payment is a signature check, not a blockchain wait — and delivery is your call per route: serve immediately and settle in the background, or hold until settlement confirms, which lands in seconds.
What about refunds and disputes?
A settled payment is final — like cash, there’s no chargeback machinery in the protocol. At per-request prices that’s a feature: the blast radius of a bad call is a cent, not an invoice cycle. Refunding is just another transfer if you choose to make one; no third party can force it either way.
Where does the money actually live?
Clients pay from a wallet — a keypair holding USDC, not an account with a provider
(crosshatch profile add makes one for development). Merchants receive at the
address on the requirement: yours on settlement, in seconds, no payout schedule.
Is x402 real? Who’s behind it?
An open protocol with an open spec, started at Coinbase, adopted by a growing ecosystem of paid APIs and agent platforms. Crosshatch implements v2 and is Apache-2.0 — the protocol is data; there’s no lock-in.
Get started
Pick a side. Or both.
Pay for x402 resources
Set up a payer, wire the client layer, and consume paid APIs from any Effect program — or hand your agent a wallet.
Client quickstartCharge for your routes
Declare a price, return 402 until paid, settle, and serve — in one route handler.
Merchant quickstartBuilding with a coding agent? Point it at crosshatch.dev/llms-full.txt — the full docs as one file.