Agents calling APIs have no checkout page or API key signup — they have a wallet. We built an x402 payment gateway on Arc: request → 402 challenge → signed USDC payment → resource, in one round trip. Two pilot routes live behind a flag ($0.10 passport, $0.02 FX feed), legacy rails untouched. Then an adversarial AI audit found the hole our 34 green tests missed: payment settled on-chain, accounting crashed, payer got a 500 with no receipt and a spent nonce. 1 Critical + 6 High + 9 Medium — all fixed with regression tests.
Agents that call APIs have no checkout page, no credit card, and no API key signup — so we built an x402 payment gateway that charges per request in USDC on Arc. The flow is HTTP's forgotten 402 status finally doing its job: request → 402 challenge → signed payment → resource. Two pilot routes went live behind a flag: $0.10 for a passport issuance, $0.02 for a premium FX feed — while the legacy routes stay untouched. Then we pointed an adversarial AI audit at the money path, and it found the hole that would have taken real funds: payment settled on-chain, accounting crashed, payer got a 500 with no resource and no receipt — and the spent nonce made a retry impossible. One Critical, six High, nine Medium findings, all fixed with regression tests.
Why would a machine need a cash register? #
Because every other way of charging for an API assumes a human. Stripe checkout assumes a person with a card and a browser. API keys assume someone who registered an account first. An autonomous agent has none of that — it has a wallet. x402 is the pattern where HTTP status 402 Payment Required becomes real: the server answers a request not with a login page but with a price tag, and the client retries the same request carrying a cryptographic payment proof.
For a junior: think of a vending machine instead of a supermarket. No account, no cart, no checkout wizard. You walk up with exact change, the machine prices the item, takes the coin, and gives you the thing plus a receipt — all in one interaction. Our gateway is that vending machine for HTTP resources, built on the payment rails from the previous episodes.
How does the 402 loop work? #
In four layers, bottom to top. Everything the client sees is derived server-side — the price, the network, the recipient — so a client can never say "I paid" for a resource that wasn't charged.
Domain types live in the payments package; transport lives in the server; settlement stays in the existing seam.
- Registry — a
PaidResourcedeclaresid, method, path, priceUsd, network. Unregistered routes pass the gate untouched, so the gateway is strictly opt-in per route. - Price resolution — a catalog-first
PriceResolver, falling back to the registry's declared price. Resolved USD is converted to USDC atomic units at the boundary. - Service facade — builds the payment requirements, delegates verify+settle to the existing x402 seam (the same one the legacy routes use — we didn't reimplement settlement), appends a
UsageRecord, and resolvesagentIdfrom the payer's wallet binding — ornullwhen unbound. - Middleware — the Hono gate. No
PAYMENT-SIGNATUREheader → spec-compliant402 + PAYMENT-REQUIRED. Signature present → settle → record usage →PAYMENT-RESPONSEreceipt header → serve the resource.
One round trip for the price tag, one for the goods. The receipt travels in the response header.
What did the adversarial audit break? #
After the pilots shipped — 30 unit tests + 8 end-to-end tests, all green — we ran an Arc Studio adversarial review. Verdict: REQUEST CHANGES — 1 Critical, 6 High, 9 Medium. The green tests had covered the happy path and the "payment rejected" path. The audit covered the third path: payment accepted, then something else breaks.
- C-1 — paid but nothing received. Settlement succeeded on-chain, then a database hiccup in the usage-record write threw a bare 500. The payer's nonce was already spent, so a retry could never recover — no resource, no record, no receipt. Now: post-settle accounting is wrapped so a paid requestalways serves the resource and emits
PAYMENT-RESPONSE; failures are logged as reconciliation records instead. - H-1 — the free HEAD request. Hono answers
HEADthrough theGEThandler, but our route lookup matched methods exactly —HEAD /api/v1/fx/delta/premiumskipped the gate entirely. Now HEAD is normalized to GET before lookup. - H-2 — client-controlled dedupe key.
x-request-idanchored usage dedupe; a client could reuse it across different payments and silently suppress records. The anchor is now the settlement tx — cryptographically unique per payment. - H-3 — charge first, fail later. Settlement ran before the handler even existed in memory; a dead downstream engine meant a paid 503. A
preChecknow refuses unbootable requestsbefore the 402 challenge — you can't be charged for what can't be served. - H-5 — wrong attribution. The usage record wrote the registry's declared network instead of what actually settled. Now it records
settle.sourceChain, and the advertised accepts are filtered to the resource's own network. - H-6 — a mount that leaked.
app.route("/api/v1", passportRoutes)exposed every future passport route under the paid prefix. The pilot now mounts the single handler explicitly.
The audit found the gap between "payment succeeded" and "resource served" — the seam every money path is made of.
What's live and how do I try it? #
Behind X402_GATEWAY_ENABLED on Arc testnet — Arc USDC only in v1:
POST https://agentbadge.xyz/api/v1/passport/request—$0.10, issues an agent passport (same handler as the legacy route)GET https://agentbadge.xyz/api/v1/fx/delta/premium—$0.02, premium FX-delta snapshot +payment: {txHash, network, amount, receiptId}in the body
The two pilot slots: same handlers as the legacy rails, now reachable by a machine carrying coins.
Registering a new paid resource is a registry entry, not a new code path: { id, method, path, priceUsd, network }. The middleware, challenge, settlement, usage record and receipt come free.
What are the honest limits? #
- Pilots, not production. Two routes, flag-gated, Arc testnet. The legacy passport rail and the Celo FX gate are untouched on purpose — migration is additive, never a silent swap.
- No live numbers yet. End-to-end is proven with the seam stubbed at the boundary (8 e2e tests). Real testnet txs through the gateway haven't run at press time — we say so rather than quote a receipt count we don't have.
- Bearer payments. A valid signature pays; it isn't bound to a specific resource by the wire protocol. That's x402's design, and the mitigation lives in the requirements the server derives, not in the client's wallet.
- One audit. Adversarial review we fixed and re-verified, not a formal verification. The regression tests — HEAD gating, zero-price, post-settle crash, engine-off refusal — are the artifact that outlives this article.
The deeper principle is the same one the reconciliation episode ended on: a chain payment isn't an event you receive, it's a claim you verify — and this episode adds the other half: once verified, everything after the money moves is customer service, and it must never cancel the sale.