# We Gave Our API a Cash Register. Then We Asked an AI to Rob It — x402 Gateway on Arc

> Source: <https://agentbadge.xyz/blog/arc-c50-x402-gateway>
> Published: 2026-11-14 00:00:00+00:00

# We Gave Our API a Cash Register. Then We Asked an AI to Rob It — x402 Gateway on Arc

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.* 

1.  **Registry** — a`PaidResource` declares`id, method, path, priceUsd, network` . Unregistered routes pass the gate untouched, so the gateway is strictly opt-in per route.
2.  **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.
3.  **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 resolves`agentId` from the payer's wallet binding — or`null` when unbound.
4.  **Middleware** — the Hono gate. No`PAYMENT-SIGNATURE` header → spec-compliant`402 + PAYMENT-REQUIRED` . Signature present → settle → record usage →`PAYMENT-RESPONSE` receipt 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 request*always* serves the resource and emits`PAYMENT-RESPONSE` ; failures are logged as reconciliation records instead.
-  **H-1 — the free HEAD request.** Hono answers`HEAD` through the`GET` handler, but our route lookup matched methods exactly —`HEAD /api/v1/fx/delta/premium` skipped the gate entirely. Now HEAD is normalized to GET before lookup.
-  **H-2 — client-controlled dedupe key.**`x-request-id` anchored 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`preCheck` now refuses unbootable requests*before* 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.*
