x402 from scratch: paying for the web one HTTP request at a time
There's a number in the HTTP spec that's been sitting unused since 1997. 402 Payment Required — reserved "for future use," never standardized, dead text in the RFC for 28 years. The future took a stablecoin to show up.
I built on it — an x402-gated betting endpoint at B3 — so I'll tell you exactly how the machine works. And then I'll tell you the part nobody puts on the landing page: the ecosystem is valued in the billions and it's moving roughly the GDP of a lemonade stand. Both things are true, and that's what makes it worth writing down.
The status code that waited 28 years
The pitch is seductive. A server says that'll be half a cent, your agent signs a message, the resource appears. No account. No API key. No invoice. No redirect to a checkout page. No gas.
x402 — open-sourced by Coinbase in May 2025, and as of April 2, 2026 donated to a neutral x402 Foundation under the Linux Foundation — is the spec that finally gives 402 semantics. The trick is almost insultingly simple: terms go in a response header, payment goes in a request header. The protocol doesn't move money. It standardizes the conversation about money, and lets the actual settlement happen on a chain underneath.
The reason it matters now isn't humans. It's agents. A human hits a paywall a few times a day and grumbles. An agent making a thousand API calls an hour can't sign up for a thousand free trials, can't wait on a monthly invoice, can't hold a credit card. It needs to pay per request, unattended, in the same breath as the request itself. That's the demand x402 is built for — and, as we'll get to, the demand that hasn't quite arrived.
The loop, end to end
One paid request is four messages. Watch it move:
GET /resource HTTP/1.1
host: api.example.com
accept: application/jsonpayment is just the same GET, retried with one signed header. no account, no redirect, no gas for the agent.
In headers, the round trip looks like this. The agent fires a normal request:
GET /resource HTTP/1.1
host: api.example.comThe server doesn't have a payment, so it quotes terms and stops:
HTTP/1.1 402 Payment Required
content-type: application/json
{ "x402Version": 1,
"accepts": [{
"scheme": "exact",
"network": "base",
"maxAmountRequired": "1000", // 0.001 USDC, 6 decimals
"asset": "0x833589fCD6...02913", // USDC on Base
"payTo": "0x9f2A...c41D",
"maxTimeoutSeconds": 60
}]
}The agent reads the terms, signs an authorization (more on the what below), base64-encodes it, and retries the exact same request with one extra header:
GET /resource HTTP/1.1
host: api.example.com
X-PAYMENT: eyJzY2hlbWUiOiJleGFjdCIsInBheWxvYWQiOnsic2lnbmF0dXJl...The server hands that blob to a facilitator, which settles it on-chain, and then — only then — returns what you asked for:
HTTP/1.1 200 OK
x-payment-response: { "success": true, "txHash": "0x4b9c...e017" }
{ "resource": "...the bytes you paid for..." }That's it. The whole thing. Payment is a retried GET with a signed header. There is no second protocol, no wallet pop-up, no OAuth dance. The aha I want you to hold onto: from the agent's point of view, paying and fetching are the same verb.
Why EIP-3009 and not the alternatives
The interesting engineering is in that signed blob. The agent has to authorize a USDC transfer without sending a transaction itself — because sending a transaction means holding ETH for gas, and "your agent needs a funded gas wallet on every chain" defeats the entire accountless pitch. So which signing primitive gets you there?
approve+transferFrom(the ERC-20 classic) needs two transactions and the spender must be pre-approved — and the agent still pays gas to callapprove. Out.permit(EIP-2612) is gasless to sign, but it only sets an allowance. Someone still has to calltransferFromafterward, and the approval sits there as standing risk. Half a solution.- EIP-3009
transferWithAuthorizationis the one. The signer authorizes a specific transfer — exactto, exactvalue— and any relayer can submit it. The signer never touches the chain. The relayer pays gas.
So the derivation is forced: gasless and accountless and single-shot — pick a primitive that gives all three, and only transferWithAuthorization does. That's why x402's exact scheme on EVM is built on it. The full rules are in the exact-scheme spec.
The typed message, field by field
Here is the literal object the agent signs. This is the load-bearing snippet of the whole post — read the comments:
const domain = {
name: "USD Coin",
version: "2",
chainId: 8453, // Base mainnet
verifyingContract: USDC_ADDRESS, // binds the sig to *this* token
};
const types = {
TransferWithAuthorization: [
{ name: "from", type: "address" },
{ name: "to", type: "address" },
{ name: "value", type: "uint256" },
{ name: "validAfter", type: "uint256" },
{ name: "validBefore", type: "uint256" }, // leaked-sig exposure window
{ name: "nonce", type: "bytes32" }, // random; burned on first use
],
};
const message = {
from: agent.address,
to: payTo, // the server's address, from the 402
value: 1000n, // 0.001 USDC (6 decimals)
validAfter: 0,
validBefore: Math.floor(Date.now() / 1000) + 60,
nonce: crypto.getRandomValues(new Uint8Array(32)), // never reuse
};
const signature = await wallet.signTypedData({
domain, types, primaryType: "TransferWithAuthorization", message,
});Every field is doing security work. to and value are fixed inside the signature — the facilitator physically cannot redirect the funds or inflate the amount without invalidating it. validBefore is a stopwatch: if the signed payload leaks, it's only live for those 60 seconds. And nonce is the whole replay story, which deserves its own section.
This is all EIP-712 typed structured data, which means the signature isn't over an opaque hash — it's over a human-legible struct, with the wallet able to show the user exactly what they're authorizing. The digest the agent actually signs is:
where the domain separator folds in the token name, version, chainId, and the contract address. That last part is the quiet guarantee: domain separation binds the signature to this token on this chain. A signature for USDC-on-Base can never be replayed against USDC-on-Polygon, because the chainId ( vs ) and the verifyingContract differ, so the digest differs, so ecrecover returns a different address, so it reverts. You get cross-chain replay protection for free, baked into the math.
The facilitator: who pays the gas
The facilitator is the piece that makes "gasless" true. It does three things: verify the signature is valid for the quoted terms, submit transferWithAuthorization to the token contract on Base, and report back to the resource server whether settlement succeeded. The resource server never has to touch a chain or hold a private key — it just calls /verify and /settle.
This is the trust hinge, so be precise about what the facilitator can and can't do. It cannot steal your money — the authorization names to and value, both signed, both immutable. The facilitator submitting your payload can only move exactly 0.001 USDC to exactly the server's address, or nothing. What it can do is refuse to submit (censor you) and watch every payment flow through it (learn your graph). That's the centralization seam, and it's worth saying out loud: x402 decentralizes the settlement and centralizes the relay. Different facilitators competing is the only thing that keeps that honest.
And "gasless" is precise too: it's gasless for the agent. Gas is real, Base is cheap but not free, and the facilitator eats it on every settle. For an agent making N calls at price p with facilitator fee f, the books are:
where g is the per-settle gas the facilitator pays. Today f is usually — facilitators are subsidizing adoption — which means facilitator_pnl is negative and the long-run equilibrium is f → g. Nobody's solved who sustainably pays for the relay. Flag that as unsettled; it's a real open question, not a detail.
Replay protection is the whole safety story
The seductive-but-wrong intuition is signed equals safe. It isn't. A signed transferWithAuthorization is a bearer instrument — anyone who has the bytes can submit them. If signatures could be replayed, a leaked payload would drain value on every resubmit until the wallet ran dry.
Two fields kill that. The nonce is a random 32-byte value, and the token contract tracks it: the first time transferWithAuthorization runs for a given (from, nonce), it marks that nonce used and any second attempt reverts with authorization is used or canceled. Burn-on-use. And validBefore bounds the window before the nonce is even spent — a leaked-but-unused signature self-expires.
In the demo above, hit replay this signature and re-fire a spent nonce: the facilitator step flips red and the contract reverts. That revert is the entire safety model made visceral. The signature was perfectly valid. It just wasn't fresh, and freshness is the property that matters.
The honest adoption gap
Now the sentence I promised. The mechanism is elegant. The demand is not here.
As of early 2026, on-chain x402 volume is running on the order of ~$28K a day — against an ecosystem that's been valued, by various trackers, in the billions. CoinDesk put it plainly: the demand "is just not there yet." Chainalysis's look at agentic payments tells the same story from the data side — lots of wallets, lots of infrastructure, thin real usage.
Let me be specific about a number that gets misused. You'll see "100M+ cumulative Base transactions" cited near x402. That figure is ecosystem-cumulative for Base, not x402 throughput — don't let the big number imply the protocol is busy. And a meaningful slice of the x402 volume that does exist is testing and wash — agents paying themselves, demos paying demos, leaderboard farming. I'll say it plainly: a lot of it is gamed. (I'm reverse-engineering the testing-vs-real split from public dashboards and reporting, not an official breakdown — treat the exact ratio as an estimate.)
I'm not saying this to dunk on it. I shipped on the thing. I'm saying it because the honest version is more useful than the pitch deck: this is a rail waiting for traffic, and you should build on it with eyes open about which of those two words currently applies.
Where this points
Here's the bet. The day a fleet of agents genuinely needs to pay each other a tenth of a cent, a thousand times a second, without ever opening an account — and that day is coming, not here — this is the rail they'll use. Gasless, stateless, accountless, settling in a stablecoin, riding a status code that waited 28 years for a reason to exist. The primitive is right even though the volume is wrong.
At B3 I work on crypto agents, and x-402-bets was me kicking the tires on exactly this: an endpoint an agent can pay per call, no relationship, no signup, just a signed header and a 200. The plumbing works. It's clean. What it's missing is a million agents that have something to buy from each other — and that's a product problem now, not a protocol problem, which is the most hopeful thing I can say about it.
The rail is real, gasless, and stateless. The traffic isn't here yet — but when you build the thing that needs it, send me the tx hash.