Cloudflare Workers run JavaScript at the edge in 300+ cities worldwide. Unlike traditional serverless (AWS Lambda, Google Cloud Functions), Workers execute in V8 isolates with sub-10ms cold start and no regional pinning. For AI agents that need cellular connectivity via x402, this means payment challenges can be handled in the same execution context as the agent logic — no round-trip to a centralized API gateway.
This guide walks through integrating x402 payment flows into a Cloudflare Worker, from basic 402 challenge responses to full eSIM activation with USDC settlement on Polygon. We assume you're familiar with Workers basics (fetch handler, KV namespaces, Durable Objects) and have read the x402 quickstart.
Why Cloudflare Workers for x402
Typical x402 flows require three network hops: agent → facilitator → eSIM provider → settlement. When the agent runs in a centralized cloud region (us-east-1, eu-west-1), each hop adds 50-150ms of latency. For high-frequency agents — think IoT coordinators polling connectivity status every 30 seconds — this compounds.
Workers eliminate the first hop. The agent's fetch logic and the x402 challenge handler run in the same isolate. When an eSIM purchase is needed, the Worker issues the 402 challenge, receives the payment proof, and forwards the activation request to our facilitator — all from the edge POP closest to the requesting device. In our benchmarks, this cuts P50 latency from 280ms (Lambda us-east-1 → Coinbase facilitator → eSIM dispatch) to 140ms (Workers edge → same path). P99 improvement is larger: 890ms → 420ms.
Second advantage: global deployment with zero config. A single wrangler deploy publishes your Worker to every Cloudflare edge location. Multi-region Lambda requires manual region replication, load balancers, and cross-region KV sync. Workers KV handles this automatically.
Third: cost. Workers bill per request (100k requests free, $0.50 per million after). Lambda bills per GB-second. For x402 flows — short-lived, stateless payment challenges — Workers are 3-8x cheaper at scale.
Tradeoff: Workers have a 50ms CPU time limit (extended to 5 minutes with Unbound, but that's $2 per million requests). If your agent does heavy on-chain signature verification or HD wallet derivation, you'll hit the cap. Our facilitator handles signature checks, so the Worker only validates the 402 response shape and forwards the proof. This fits comfortably in 12-18ms.
Architecture overview
A typical x402 Worker has three routes:
POST /esim/purchase— initiates an eSIM buy. Returns402 Payment Requiredwith a Coinbase facilitator challenge.POST /esim/activate— receives the payment proof (stablecoin tx hash + agent signature), validates it, and triggers eSIM activation.GET /esim/status/:iccid— queries activation status. Not payment-gated (read-only).
The Worker stores pending challenges in KV (keyed by challenge_id, 5-minute TTL). When the agent submits payment proof, we verify the tx hash against the expected USDC amount on Polygon, then call our eSIM dispatch API.
Here's a Mermaid sequence diagram:
sequenceDiagram
participant Agent
participant Worker
participant KV
participant Facilitator
participant Chain
participant eSIM
Agent->>Worker: POST /esim/purchase {country, plan}
Worker->>KV: store challenge{id, amount, ts}
Worker->>Agent: 402 Payment Required + challenge
Agent->>Chain: send USDC tx
Agent->>Worker: POST /esim/activate {proof, tx_hash}
Worker->>KV: lookup challenge by id
Worker->>Facilitator: validate proof
Facilitator->>Chain: query tx receipt
Chain-->>Facilitator: confirmed
Facilitator-->>Worker: valid
Worker->>eSIM: POST /dispatch {iccid, plan}
eSIM-->>Worker: 200 OK {activation_code}
Worker->>KV: delete challenge
Worker->>Agent: 200 OK {iccid, code}
The critical path is Worker → Facilitator → Chain → eSIM. We cache Chain lookups at the Facilitator level (Polygon block confirmations take 2-3 seconds; we don't wait for 12 confirmations on $0.80 eSIM purchases). The Worker itself is stateless except for the KV challenge store.
Code walkthrough
We'll build this in TypeScript. Install wrangler and create a new Worker:
npm install -g wrangler
wrangler init esim-x402-worker
cd esim-x402-worker
npm install @cloudflare/workers-types
Open src/index.ts. Here's the skeleton:
export interface Env {
CHALLENGES: KVNamespace;
FACILITATOR_URL: string;
FACILITATOR_API_KEY: string;
ESIM_DISPATCH_URL: string;
ESIM_API_KEY: string;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/esim/purchase' && request.method === 'POST') {
return handlePurchase(request, env);
}
if (url.pathname === '/esim/activate' && request.method === 'POST') {
return handleActivate(request, env);
}
if (url.pathname.startsWith('/esim/status/') && request.method === 'GET') {
const iccid = url.pathname.split('/').pop();
return handleStatus(iccid!, env);
}
return new Response('Not Found', { status: 404 });
}
};
The Env interface defines KV bindings and secrets. In wrangler.toml, add:
name = "esim-x402-worker"
main = "src/index.ts"
compatibility_date = "2026-06-24"
[[kv_namespaces]]
binding = "CHALLENGES"
id = "<your_kv_namespace_id>"
[vars]
FACILITATOR_URL = "https://facilitator.x402.coinbase.com/v1"
ESIM_DISPATCH_URL = "https://api.esimx402.com/v1/dispatch"
# Secrets — set via: wrangler secret put FACILITATOR_API_KEY
# FACILITATOR_API_KEY = "..."
# ESIM_API_KEY = "..."
Now implement handlePurchase. This generates a 402 challenge:
async function handlePurchase(request: Request, env: Env): Promise<Response> {
const body = await request.json() as { country: string; plan: string };
// Lookup plan pricing — in prod, query from KV or D1
const plans: Record<string, number> = {
'1gb-7day': 0.80,
'3gb-30day': 2.40,
'10gb-30day': 7.20
};
const amountUSDC = plans[body.plan];
if (!amountUSDC) {
return new Response('Invalid plan', { status: 400 });
}
// Generate challenge ID (use crypto.randomUUID in prod)
const challengeId = crypto.randomUUID();
const challenge = {
id: challengeId,
amount: amountUSDC,
currency: 'USDC',
chain: 'polygon',
recipient: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', // our facilitator addr
country: body.country,
plan: body.plan,
created_at: Date.now()
};
// Store in KV with 5min TTL
await env.CHALLENGES.put(challengeId, JSON.stringify(challenge), {
expirationTtl: 300
});
return new Response(JSON.stringify(challenge), {
status: 402,
headers: {
'Content-Type': 'application/json',
'WWW-Authenticate': `X402 realm="eSIM purchase", challenge_id="${challengeId}"`
}
});
}
Key points:
- We return
402 Payment Requiredwith the challenge object in the body. TheWWW-Authenticateheader is part of the x402 spec — agents parsechallenge_idfrom it. - The challenge includes
recipient(our Polygon wallet),amount, andchain. Agents use this to construct the USDC transfer. - We store the challenge in KV with a 5-minute TTL. After 5 minutes, the challenge expires and the agent must retry.
Next, implement handleActivate. This validates the payment proof and activates the eSIM:
async function handleActivate(request: Request, env: Env): Promise<Response> {
const body = await request.json() as {
challenge_id: string;
tx_hash: string;
proof_signature: string; // agent's signature over (challenge_id, tx_hash)
};
// Retrieve challenge from KV
const challengeData = await env.CHALLENGES.get(body.challenge_id);
if (!challengeData) {
return new Response('Challenge expired or invalid', { status: 404 });
}
const challenge = JSON.parse(challengeData);
// Validate proof with facilitator
const validationResp = await fetch(`${env.FACILITATOR_URL}/validate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${env.FACILITATOR_API_KEY}`
},
body: JSON.stringify({
challenge_id: body.challenge_id,
tx_hash: body.tx_hash,
proof_signature: body.proof_signature,
expected_amount: challenge.amount,
expected_currency: challenge.currency,
expected_chain: challenge.chain
})
});
if (!validationResp.ok) {
const error = await validationResp.text();
return new Response(`Payment validation failed: ${error}`, { status: 402 });
}
const validation = await validationResp.json() as { valid: boolean; tx_confirmed: boolean };
if (!validation.valid || !validation.tx_confirmed) {
return new Response('Payment not confirmed on-chain', { status: 402 });
}
// Activate eSIM
const activationResp = await fetch(`${env.ESIM_DISPATCH_URL}/activate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': env.ESIM_API_KEY
},
body: JSON.stringify({
country: challenge.country,
plan: challenge.plan
})
});
if (!activationResp.ok) {
return new Response('eSIM activation failed', { status: 500 });
}
const activation = await activationResp.json() as { iccid: string; activation_code: string };
// Delete challenge (one-time use)
await env.CHALLENGES.delete(body.challenge_id);
return new Response(JSON.stringify(activation), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
}
This is the critical path. The Worker:
- Retrieves the challenge from KV (fails if expired).
- Calls the Coinbase facilitator's
/validateendpoint with the tx hash and signature. The facilitator queries Polygon to confirm the USDC transfer. - If valid, calls our internal eSIM dispatch API to provision the profile.
- Returns the ICCID and activation code to the agent.
- Deletes the challenge from KV (prevents replay attacks).
Production note: the facilitator API is authenticated with FACILITATOR_API_KEY. In our setup, this is a Coinbase API key provisioned for x402 services. You'll receive this when you register with the facilitator network (see x402 documentation for onboarding steps).
Status endpoint
The status route is simpler — no payment required:
async function handleStatus(iccid: string, env: Env): Promise<Response> {
const statusResp = await fetch(`${env.ESIM_DISPATCH_URL}/status/${iccid}`, {
headers: { 'X-API-Key': env.ESIM_API_KEY }
});
if (!statusResp.ok) {
return new Response('ICCID not found', { status: 404 });
}
const status = await statusResp.json();
return new Response(JSON.stringify(status), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
}
This proxies to our eSIM backend. Agents use it to poll activation status after receiving the ICCID. Typical response shape:
{
"iccid": "8901260123456789012",
"status": "active",
"plan": "3gb-30day",
"expires_at": "2026-07-24T00:00:00Z",
"data_used_mb": 142
}
Deployment and testing
Deploy the Worker:
wrangler secret put FACILITATOR_API_KEY
# paste your Coinbase facilitator key
wrangler secret put ESIM_API_KEY
# paste your eSIMx402 dispatch key
wrangler deploy
This pushes to *.workers.dev. For production, add a custom domain in the Cloudflare dashboard.
Test the flow with curl:
# 1. Request eSIM purchase
curl -X POST https://esim-x402-worker.yourname.workers.dev/esim/purchase \
-H "Content-Type: application/json" \
-d '{"country": "US", "plan": "1gb-7day"}'
# Response: 402 with challenge object
# {"id":"550e8400-e29b-41d4-a716-446655440000","amount":0.8,"currency":"USDC",...}
# 2. Agent sends USDC on Polygon (not shown — use agent's wallet)
# tx_hash = "0xabc123..."
# 3. Submit payment proof
curl -X POST https://esim-x402-worker.yourname.workers.dev/esim/activate \
-H "Content-Type: application/json" \
-d '{
"challenge_id": "550e8400-e29b-41d4-a716-446655440000",
"tx_hash": "0xabc123...",
"proof_signature": "0xdef456..."
}'
# Response: 200 with ICCID and activation code
# {"iccid":"8901260123456789012","activation_code":"LPA:1$sm-dp-plus.example.com$..."}
For agent integration, see our LangChain tool example — it wraps this Worker API in a structured tool that autonomous agents can invoke.
Edge cases and production hardening
Challenge replay attacks: We delete the challenge from KV after successful activation. If an attacker intercepts the payment proof and tries to reuse it, the KV lookup fails. Additional safeguard: the facilitator tracks used tx hashes and rejects duplicates within a 24-hour window.
KV consistency: Workers KV is eventually consistent (writes propagate in <60 seconds globally). For high-frequency agents hitting different edge POPs, there's a risk of a challenge write at POP A not being visible at POP B. Mitigation: use Durable Objects for strongly consistent storage, or add a X-Cloudflare-POP hint to route the agent's activate request to the same POP that served the purchase. In practice, agents retry on 404, so this is rarely an issue.
Facilitator downtime: If the Coinbase facilitator is unreachable, we return 503 to the agent. Agents should implement exponential backoff. Our P99.9 facilitator uptime since Feb 2026 is 99.97% (3 hours of planned maintenance, zero unplanned).
Gas price spikes: Polygon gas is typically <0.01 USDC per transaction. During network congestion (e.g., NFT mints), gas can spike to 0.10 USDC. The facilitator passes gas costs through; if the agent's USDC transfer doesn't cover amount + gas, validation fails. Agents should add a 10% buffer when constructing the transfer.
Rate limiting: Workers have no built-in rate limiter. For production, wrap the fetch handler in a Durable Object-based limiter or use Cloudflare's Rate Limiting product (10k requests/min free tier). We see <2 req/sec per agent for typical eSIM workflows, so this rarely binds.
Performance benchmarks
We ran 10k simulated agent requests against a Workers deployment in 15 regions. Metrics:
- P50 end-to-end latency (purchase → activate): 140ms (vs 280ms for Lambda us-east-1).
- P99 latency: 420ms (vs 890ms for Lambda).
- Cold start overhead: 8ms median (Workers isolate vs 180ms Lambda container).
- Cost per 1M requests: $1.20 (Workers bundled + KV reads) vs $4.80 (Lambda + DynamoDB).
The largest latency contributor is the facilitator → Polygon query (60-90ms for tx confirmation). We're working with Coinbase on a regional facilitator deployment to bring this under 40ms.
Next steps
This integration is production-ready for single-agent deployments. For multi-agent coordinators or high-throughput IoT scenarios, consider:
- Durable Objects for wallet management: If each agent has a unique wallet, use a Durable Object per agent to cache HD wallet keys and avoid re-deriving on every request.
- Batch activation: Agents can submit multiple eSIM purchase requests in a single HTTP call. Modify
handlePurchaseto accept an array of plans and return multiple challenges. - Regional pricing: Add a GeoIP lookup (via
request.cf.country) to serve region-specific plans. KV stores plan matrices keyed by country code.
For full API reference and agent integration patterns, see the eSIMx402 documentation. The complete Worker source (including Durable Object wallet manager) is on GitHub at esimx402/cloudflare-worker-example.