Most x402 content online is either marketing (Coinbase, AWS) or hackathon tutorials. This post is neither — it documents the architecture behind a production x402 service that has been running real eSIM orders since early 2026.
We chose eSIM as the first vertical because the unit economics fit x402 perfectly: an order is $0.55 to $50, settlement is permanent (the eSIM is provisioned once the wholesale provider issues an ICCID), and there's a real demand from AI travel agents and IoT devices that none of the legacy reseller APIs can serve.
Stack at a glance
The full request lifecycle, from POST /api/v1/x402/order to QR code delivery, touches four sub-systems:
- API gateway (FastAPI behind Cloudflare) — parses the request, holds the order in
PENDING_PAYMENTstate, returns the 402 with payment details - Facilitator — Coinbase x402 facilitator on Polygon for USDC/USDT, and a TON-native facilitator we wrote in-house for USDT-on-TON
- Provisioning worker — listens for payment confirmations, makes the wholesale call to eSIM Access, stores the ICCID + QR data
- Polling endpoint —
GET /api/v1/x402/order/{id}returns the current state with strong cache headers so polling clients don't hammer the DB
All four are stateless. State lives in Postgres (orders) and Redis (idempotency keys + payment confirmation cache).
Facilitator selection — why we run both Coinbase and a custom TON one
Coinbase's x402 facilitator service is excellent for Polygon, Base, Arbitrum, and World. It's free under 1,000 transactions per month and handles the heavy lifting: payment proof verification, replay protection, chain re-org guarding.
But TON isn't in Coinbase's facilitator coverage as of May 2026. We wrote a small TON facilitator ourselves because half of our prepaid-stablecoin users hold USDT on TON (it's the dominant USDT chain among travelers).
The custom TON facilitator is ~200 lines of Python: subscribe to TON's LiteServer for incoming USDT transfers to our deposit addresses, verify the destination matches an active order, and emit a Redis event the provisioning worker listens for.
We didn't bridge TON to Polygon because:
- Bridging adds 60-300s latency, killing the agent-perceived response time
- Bridge fees would eat the margin on $1-5 orders entirely
- TON's native USDT is liquid enough that holders aren't asking us to "just bridge"
If you're building a similar service, picking facilitators per chain (rather than wrapping everything in a single bridge) is the right call for cost and latency.
The payment-state machine
An order moves through six states:
NEW → just created, no payment yet
PENDING_PAYMENT → 402 returned, payment window open
PAID → on-chain payment confirmed by facilitator
PROVISIONING → wholesale eSIM provider working
DELIVERED → ICCID + QR code stored, order complete
EXPIRED / REFUNDED → terminal failure states
The interesting transitions:
- NEW → PENDING_PAYMENT: idempotent. If the same client retries within 60s with the same body, we return the same order ID + same payment details. Critical for agents that auto-retry on flaky networks.
- PENDING_PAYMENT → PAID: triggered by facilitator webhook OR by our background polling loop (whichever fires first). We re-verify the payment proof server-side even when the facilitator already confirmed — defense in depth.
- PAID → PROVISIONING → DELIVERED: synchronous from PAID. If the wholesaler is slow, the order sits in PROVISIONING; the client just keeps polling. Median PAID→DELIVERED is 2.4 seconds.
- PROVISIONING → REFUNDED: if the wholesaler fails 3 retries (~30s of internal back-off), we kick off an on-chain refund to the payer's address. Refund target time is 5 minutes.
Idempotency is enforced at the DB level via a unique constraint on (client_ip, plan_id, created_at_minute). Coarser than "exact body match" but eliminates the most common double-spend scenario (agent retries with same parameters).
What we got wrong (initially)
Three things broke in real traffic that the tutorials don't warn about:
1. Chain re-orgs on Polygon. First version trusted Coinbase facilitator confirmation immediately. When Polygon had a 6-block reorg in March 2026, three orders briefly appeared paid then unpaid. Now we wait for 6 confirmations on Polygon (~12 seconds extra latency) before marking PAID.
2. Wallet rounds to integer USDC. Several agent wallets we tested round payment amounts to integer USDC. Order quoted at $6.21 → wallet sends $6 → we'd reject as underpayment. Now we accept payments within ±$0.50 of the quoted amount, with the difference auto-refunded on PAID transition. The margin loss is negligible vs the conversion loss from rejected orders.
3. ICCID assignment is non-refundable. Once the wholesale provider issues an ICCID, we owe them payment — even if the user never activates the eSIM. So we delay the wholesaler call until the payment is fully confirmed. Earlier versions called the wholesaler optimistically during PENDING_PAYMENT to shave latency; that pattern cost us ~$200 in unrecoverable ICCIDs in week 1 before we caught it.
What's next
The current bottleneck isn't infrastructure — it's distribution. Most agents that would buy eSIM data don't know x402 services exist. We're testing two paths: tighter integrations with AWS Bedrock AgentCore Payments (which launched May 7) and World AgentKit. Both treat x402 as the underlying transport, which means we don't need agent-side SDK work — just clean documentation of our endpoints.
If you're building an agent that needs cellular data, the API is documented at /docs. The reference Python and Node clients we use internally will go open-source in the next two weeks. Questions: dev@esimx402.com.