When your AI agent requests cellular connectivity via our API, the underlying machinery coordinates a four-party handshake: agent → facilitator → blockchain → eSIM carrier. This post walks through the actual dispatch flow we run on Polygon, including the tradeoffs we made versus Base and Arbitrum, the error-handling patterns that keep P50 latency under 10 seconds, and the real production numbers from May 2026.
Why Polygon for the facilitator layer
We chose Polygon (formerly Matic) as the settlement chain for our x402 facilitator after testing Base, Arbitrum, and Polygon side-by-side in Q1 2026. The deciding factors:
- Gas cost: Polygon USDC transfers averaged 0.0021 USD per transaction in April 2026. Base was 0.0034 USD, Arbitrum 0.0028 USD. For sub-dollar eSIM activations (common in our failover use case), gas delta matters.
- Block time: Polygon's 2-second average block time versus Base's 2.5 seconds gives us a slight edge in time-to-confirmation for the 402 payment receipt.
- USDC liquidity: Circle's native USDC on Polygon has deeper liquidity than bridged USDC.e on Arbitrum. We've never hit a swap ceiling, but the headroom exists.
The downside: Polygon's sequencer has had two sub-60-second outages in the past six months (March 12 and April 29, 2026). Base had zero. We accept this because our failover pattern (described in Use Cases) automatically retries on a backup facilitator if Polygon goes dark.
The four stages of dispatch
Here's the end-to-end flow when an agent calls POST /v1/esim/provision with a country code and data package size:
Stage 1: HTTP 402 challenge generation (server-side, <50ms)
Our API responds immediately with a 402 Payment Required status and a WWW-Authenticate header containing the facilitator challenge. The challenge includes:
- The Polygon contract address of our facilitator (currently
0x7a8f...3c2eon mainnet) - The USDC amount in wei (e.g.,
1000000for 1 USDC) - A nonce derived from the agent's request ID
- An expiration timestamp (5 minutes from challenge generation)
Example response headers:
HTTP/1.1 402 Payment Required
WWW-Authenticate: x402 facilitator="0x7a8f...3c2e", amount="1000000", token="usdc", chain="polygon", nonce="d4f8a2b1", expires="1748380800"
Content-Type: application/json
{"error": "payment_required", "facilitator_url": "https://facilitator.esimx402.com/pay"}
The agent's x402 client library (we ship reference implementations for LangChain and World AgentKit—see Quickstart) parses this header and constructs the on-chain transaction.
Stage 2: Agent submits USDC transfer on Polygon (agent-side, ~4s)
The agent's wallet (typically an HD-derived address from the agent's master seed) signs and broadcasts a Polygon transaction calling transfer(address,uint256) on the USDC contract (0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 on Polygon mainnet). The recipient is our facilitator contract, the amount matches the challenge.
In production, we see P50 confirmation time of 4.2 seconds from broadcast to 1-conf inclusion. The agent polls the Polygon RPC until the transaction receipt shows status: 1.
import web3
from web3 import Web3
# Agent wallet setup (simplified — real agents use AgentKit or LangChain wallet abstractions)
w3 = Web3(Web3.HTTPProvider("https://polygon-rpc.com"))
agent_address = "0xABCD...1234" # Agent's Polygon address
facilitator_address = "0x7a8f...3c2e"
usdc_contract = w3.eth.contract(
address="0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
abi=[{"constant": False, "inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "transfer", "outputs": [{"name": "", "type": "bool"}], "type": "function"}]
)
tx_hash = usdc_contract.functions.transfer(
facilitator_address,
1_000_000 # 1 USDC in wei (6 decimals for USDC)
).transact({"from": agent_address, "gas": 65000})
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=10)
assert receipt.status == 1, "Transfer failed"
This is the slowest stage in the flow. Polygon's 2-second block time plus mempool propagation accounts for ~4 seconds of our 8.3-second P50 end-to-end latency.
Stage 3: Facilitator verifies and emits eSIM dispatch event (server-side, <200ms)
Our facilitator backend runs a Polygon full node (Erigon) that indexes Transfer events on the USDC contract in real time. When we see a transfer to our facilitator address matching a pending challenge nonce, we:
- Verify the
fromaddress matches the agent's wallet from the original API request (anti-spoofing) - Verify the amount >= the challenge amount (we accept overpayment; no refund mechanism yet)
- Mark the challenge as paid in Redis (TTL = 24 hours for audit logs)
- Emit an internal event to our eSIM provisioning queue (RabbitMQ, durable queue with 3 replicas)
This stage completes in under 200ms P95. The bottleneck is the Erigon node's JSON-RPC latency (we co-locate the node in the same AWS region as the API servers—us-east-1).
Stage 4: eSIM carrier API call and activation confirmation (server-side, ~3.8s)
Our provisioning worker (written in Go, runs as a Kubernetes deployment with 8 replicas) pulls the event from RabbitMQ and calls the carrier's bulk eSIM API. We aggregate with three carriers (names redacted per NDA, but all tier-1 MNOs with global roaming agreements). The API call:
- Requests an eSIM profile matching the agent's requested country and data package
- Receives an ICCID and activation code (QR-scannable format, though agents use programmatic activation via
LPA:1$...string) - Writes the eSIM metadata to our Postgres database (encrypted at rest, agent can retrieve via
GET /v1/esim/{id})
Carrier API latency P50: 3.1 seconds. P95: 6.8 seconds. We've set a 10-second timeout; if the carrier doesn't respond, we retry once on a different carrier (adds ~4s to total latency but only happens in 2.3% of requests based on May 2026 data).
Once the carrier confirms activation, we return 200 OK to the agent's original HTTP request (which has been long-polling our /v1/esim/provision endpoint). The response includes the ICCID, activation code, and a signed JWT proving the agent paid.
Sequence diagram: full dispatch flow
sequenceDiagram
participant Agent
participant API as eSIMx402 API
participant Facilitator as Facilitator Contract (Polygon)
participant USDC as USDC Contract (Polygon)
participant Queue as RabbitMQ
participant Worker as Provisioning Worker
participant Carrier as eSIM Carrier API
Agent->>API: POST /v1/esim/provision {country, package}
API->>API: Generate challenge (nonce, amount, expiry)
API-->>Agent: 402 Payment Required + WWW-Authenticate header
Agent->>USDC: transfer(facilitator, 1 USDC)
USDC-->>Facilitator: Transfer event emitted
Facilitator->>Facilitator: Verify nonce, amount, from address
Facilitator->>Queue: Enqueue {agent_id, country, package, tx_hash}
Worker->>Queue: Dequeue event
Worker->>Carrier: POST /provision {iccid_request}
Carrier-->>Worker: 200 OK {iccid, activation_code}
Worker->>API: Mark provision complete in DB
API-->>Agent: 200 OK {iccid, activation_code, jwt}
Error handling: what breaks and how we recover
The weakest link is Stage 4 (carrier API). We've seen three failure modes in production:
Carrier timeout (2.3% of requests)
If the primary carrier doesn't respond within 10 seconds, we retry on a secondary carrier. This adds ~4 seconds to latency but succeeds 98.7% of the time. The remaining 1.3% get a 503 Service Unavailable and the agent retries the entire flow (we refund the stuck USDC manually—automation coming in Q3 2026).
Polygon RPC node lag (0.8% of requests)
Our Erigon node occasionally falls behind head by 1-2 blocks during high network activity. This delays Stage 3 verification by up to 4 seconds. We mitigate by running a hot standby Erigon node in us-west-2 and failing over if us-east-1 lags >3 blocks.
Agent wallet insufficient balance (4.1% of requests)
The agent broadcasts the USDC transfer but has insufficient balance or gas. Polygon reverts the transaction, the agent never gets a receipt, and our facilitator never sees the payment. We detect this by monitoring for challenge nonces that expire without payment and emit a payment_failed webhook to the agent's callback URL (if configured).
Production metrics (May 2026)
- Total agent transactions: 37,428
- Total USDC volume: 42,891 USDC (mix of 1 USDC and 0.50 USDC packages)
- P50 end-to-end latency: 8.3 seconds (challenge → activated eSIM)
- P95 end-to-end latency: 14.7 seconds
- Success rate: 96.4% (includes carrier retries)
- Average gas cost per transaction: 0.0021 USD (Polygon USDC transfer)
- Total gas spend (May): 78.59 USD
For comparison, our Base testnet (we ran 1,200 test transactions in April 2026 before choosing Polygon) showed P50 latency of 9.1 seconds and gas cost of 0.0034 USD per transaction. The 0.8-second latency penalty wasn't worth the 62% gas savings.
Why we don't use Coinbase's hosted facilitator
Coinbase offers a managed x402 facilitator service at facilitator.x402.org that handles Stages 2-3 for you. We evaluated it in Q4 2025 and decided to self-host because:
- Carrier integration flexibility: Coinbase's facilitator assumes you're paying for a generic API; we need to inject eSIM-specific metadata (ICCID prefix hints, roaming zone filters) into the payment flow. Self-hosting lets us customize the event payload.
- Cost: Coinbase charges 0.5% of transaction volume as a facilitator fee. For our May 2026 volume (42.8k USDC), that's $214/month. Self-hosting costs us ~$120/month in Erigon node + RabbitMQ infra.
- Latency: Coinbase's facilitator adds ~300ms to Stage 3 verification (their node is hosted in
us-west-1; most agents areus-east-1or Europe). Co-locating our facilitator with the API servers saves that RTT.
The tradeoff: we maintain the facilitator contract and Erigon node ourselves. If you're building an x402 service and don't need sub-10s latency, Coinbase's hosted facilitator is simpler.
Code: deploying your own facilitator on Polygon
If you want to replicate our setup, here's the facilitator contract (Solidity 0.8.19, OpenZeppelin 4.9). This is a simplified version—production code includes access control, pausability, and multi-sig withdrawal.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract X402Facilitator {
IERC20 public immutable usdc;
mapping(bytes32 => bool) public paidChallenges;
event PaymentReceived(address indexed from, uint256 amount, bytes32 nonce);
constructor(address _usdc) {
usdc = IERC20(_usdc);
}
function verifyPayment(address from, uint256 amount, bytes32 nonce) external {
require(!paidChallenges[nonce], "Challenge already paid");
require(usdc.transferFrom(from, address(this), amount), "Transfer failed");
paidChallenges[nonce] = true;
emit PaymentReceived(from, amount, nonce);
}
function withdraw(address to, uint256 amount) external {
// In production: add onlyOwner modifier
require(usdc.transfer(to, amount), "Withdrawal failed");
}
}
Deploy this to Polygon mainnet, fund it with gas, and point your API's challenge generation to the deployed address. Then run an Erigon node subscribed to PaymentReceived events. Full deployment guide: Docs.
What's next: multi-chain failover
We're testing a multi-chain facilitator setup where the agent can pay on Polygon, Base, or TON (via USDT) depending on which chain has the lowest gas at request time. The agent's x402 client library will query gas oracles and pick the cheapest chain. This adds complexity (three Erigon nodes, three facilitator contracts, unified nonce registry) but could cut gas costs by another 30-40% during Polygon congestion spikes.
The architecture shown here is our production v1. If you're integrating x402 for a different use case (IoT devices, multi-agent swarms, privacy-focused routing), the dispatch pattern generalizes: HTTP 402 challenge → stablecoin transfer → off-chain fulfillment → confirmation. The eSIM carrier is just one fulfillment backend. You could swap it for compute credits, API quota, or physical goods delivery.
Start building: Quickstart has a 5-minute integration example using LangChain. For pricing (spoiler: we bill agents at cost + 8% margin), see Pricing.
FAQ
What happens if the agent's USDC transfer gets stuck in the mempool?
If Polygon network congestion causes the transaction to sit unconfirmed for >5 minutes, the challenge nonce expires. The agent receives a 402 Payment Required error on retry and must generate a fresh challenge. The stuck USDC transfer (if it eventually confirms) goes into a reconciliation queue; we manually refund these once per week. Automated refunds are planned for Q3 2026.
Can agents pay with tokens other than USDC?
Currently we only accept USDC on Polygon. We're testing USDT on TON and native USDC on Base. The x402 spec allows any ERC-20; the constraint is our carrier settlement—they invoice us in USD, so we need a 1:1 stablecoin. Volatile tokens (ETH, MATIC) would require on-chain DEX swaps, adding latency and slippage risk.
How do you prevent double-spend attacks where an agent reuses a paid nonce?
The facilitator contract's paidChallenges mapping marks each nonce as consumed on first payment. If an agent tries to resubmit the same nonce, the require(!paidChallenges[nonce]) check reverts the transaction. Nonces are derived from the agent's request ID + timestamp, making collisions astronomically unlikely.
What's the minimum eSIM package size, and does it affect dispatch latency?
Minimum is 500 MB for $0.50 USDC. Latency is independent of package size—the carrier API takes the same ~3.1 seconds whether you request 500 MB or 10 GB. The ICCID assignment is pre-provisioned in the carrier's pool; we're just associating an agent's payment with an available profile.
Why not use a payment channel or state channel instead of on-chain transactions for every request?
We evaluated Lightning-style channels in Q4 2025. The complexity (channel open/close transactions, routing for multi-hop agents, watchtower infrastructure) didn't justify the gas savings for our usage pattern. Most agents provision 1-5 eSIMs per month, not 100+ micro-transactions per day. At that volume, the 0.0021 USD gas cost is negligible compared to the eSIM package cost ($0.50-$5).