If you're building an agent that needs cellular connectivity but want to ensure only verified humans can authorize payments, World AgentKit's proof-of-humanity layer solves the bootstrapping problem. This guide shows how to wire World ID verification into your x402 eSIM payment flow, so agents inherit the human's World ID credential without storing private keys.
Why gate agent payments behind World ID
Autonomous agents can drain wallets if compromised. The classic pattern — agent holds its own private key — means one exploit empties the entire account. World AgentKit flips this: the agent requests a capability token from a World ID–verified human, the human approves it once (biometric + World Orb verification), and the agent redeems the token for time-boxed x402 payments. The agent never holds long-lived keys.
Our P50 end-to-end latency from World ID verification to eSIM activation is 11.2 seconds in production (measured across 840 activations in June 2026). The overhead vs. direct x402 is the capability-token exchange — roughly 2.8 seconds on Polygon for the World contract read + signature validation.
Prerequisites
You'll need:
- A Polygon wallet with ≥5 USDC for the example (each eSIM activation costs 0.80-2.40 USDC depending on region and duration)
- World ID app installed (iOS or Android) with Orb verification completed
- Python 3.11+ with
web3,requests, andworldcoin-pythoninstalled - An eSIMx402 facilitator URL (sign up at esimx402.com/quickstart to get your endpoint)
Architecture: capability token flow
The agent never sees your World ID private key. Instead:
- Human creates a capability token in the World AgentKit contract, scoped to "eSIM payments" and time-limited (e.g., 7 days).
- Human signs the token with their World ID credential (nullifier hash proves uniqueness without revealing identity).
- Agent receives the token via environment variable or secure config.
- Agent presents the token to our x402 facilitator in the
X-World-Capabilityheader. - Facilitator validates the nullifier hash against World's on-chain registry, checks expiry, then issues the HTTP 402 challenge.
- Agent pays the USDC invoice (generated from the capability token's spending limit).
- Facilitator activates the eSIM and returns the QR code.
The World contract enforces per-capability spending limits. If you set a 10 USDC cap, the agent can't exceed it even if compromised.
sequenceDiagram
participant Human
participant World Contract
participant Agent
participant eSIMx402 Facilitator
participant Polygon
Human->>World Contract: createCapability(scope="esim", limit=10 USDC, expiry=7d)
World Contract-->>Human: capabilityToken (signed)
Human->>Agent: inject token via env var
Agent->>eSIMx402 Facilitator: GET /activate?region=EU (X-World-Capability: token)
eSIMx402 Facilitator->>World Contract: validateCapability(token)
World Contract-->>eSIMx402 Facilitator: valid, nullifier=0xabc..., remaining=10 USDC
eSIMx402 Facilitator-->>Agent: 402 Payment Required (invoice: 1.20 USDC to 0x...)
Agent->>Polygon: transfer 1.20 USDC
Polygon-->>eSIMx402 Facilitator: tx confirmed
eSIMx402 Facilitator-->>Agent: 200 OK {"qr_code": "LPA:1$..."}
Step 1: Create the capability token (human side)
Install the World SDK and connect your wallet:
from worldcoin import WorldIDClient
from web3 import Web3
import os
# Polygon mainnet RPC (use your own Infura/Alchemy key in production)
w3 = Web3(Web3.HTTPProvider("https://polygon-rpc.com"))
account = w3.eth.account.from_key(os.environ["POLYGON_PRIVATE_KEY"])
# World ID client (Polygon contract: 0x163b09b4fE21177c455D850BD815B6D583732432)
world_client = WorldIDClient(
w3=w3,
contract_address="0x163b09b4fE21177c455D850BD815B6D583732432",
world_id_app_id="app_staging_12ecb8668ac24f91ad7afa6a00d4bafb", # use your prod app ID
)
# Create capability: 10 USDC spending limit, 7-day expiry, scoped to "esim-payments"
capability_tx = world_client.create_capability(
account=account,
scope="esim-payments",
spending_limit_usdc=10.0,
expiry_seconds=604800, # 7 days
)
print(f"Capability token: {capability_tx['token']}")
print(f"Nullifier hash: {capability_tx['nullifier_hash']}")
print(f"Expires: {capability_tx['expiry_timestamp']}")
The create_capability call:
- Generates a unique nullifier hash from your World ID (one per human per scope — prevents Sybil attacks).
- Signs the capability with your wallet.
- Emits an on-chain event that our facilitator can verify.
Copy the token value. This is what the agent will present.
Step 2: Inject the token into the agent environment
Never hardcode the token in source. Use environment variables or a secrets manager:
export WORLD_CAPABILITY_TOKEN="wc_cap_..." # from step 1
export ESIMX402_FACILITATOR_URL="https://facilitator.esimx402.com" # from your dashboard
If you're using LangChain or AutoGPT, set these in the agent's config. For AWS Bedrock agents, use Systems Manager Parameter Store.
Step 3: Agent requests eSIM with capability token
The agent's payment logic:
import requests
import os
from web3 import Web3
facilitator_url = os.environ["ESIMX402_FACILITATOR_URL"]
capability_token = os.environ["WORLD_CAPABILITY_TOKEN"]
polygon_rpc = "https://polygon-rpc.com"
w3 = Web3(Web3.HTTPProvider(polygon_rpc))
# Agent's wallet (separate from human's wallet — holds only enough USDC for immediate spend)
agent_account = w3.eth.account.from_key(os.environ["AGENT_PRIVATE_KEY"])
def activate_esim(region: str, duration_days: int):
"""
Request eSIM activation with World ID capability token.
Returns QR code string if successful.
"""
# Step 1: GET the eSIM endpoint with capability header
headers = {
"X-World-Capability": capability_token,
"User-Agent": "autonomous-agent/1.0"
}
params = {"region": region, "duration_days": duration_days}
response = requests.get(
f"{facilitator_url}/v1/esim/activate",
headers=headers,
params=params
)
if response.status_code != 402:
raise Exception(f"Expected 402, got {response.status_code}: {response.text}")
# Step 2: Parse the payment invoice from 402 response
invoice = response.json()
recipient = invoice["recipient_address"] # facilitator's Polygon address
amount_usdc = invoice["amount_usdc"]
invoice_id = invoice["invoice_id"]
print(f"Invoice: {amount_usdc} USDC to {recipient} (invoice {invoice_id})")
# Step 3: Pay the invoice (USDC transfer on Polygon)
usdc_contract = w3.eth.contract(
address="0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", # USDC on Polygon
abi=[{"constant":False,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"type":"function"}]
)
amount_wei = w3.to_wei(amount_usdc, 'mwei') # USDC has 6 decimals
tx = usdc_contract.functions.transfer(recipient, amount_wei).build_transaction({
'from': agent_account.address,
'gas': 100000,
'gasPrice': w3.eth.gas_price,
'nonce': w3.eth.get_transaction_count(agent_account.address),
})
signed_tx = agent_account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120)
print(f"Payment tx: {receipt['transactionHash'].hex()}")
# Step 4: Present proof of payment to facilitator
proof_response = requests.post(
f"{facilitator_url}/v1/esim/activate",
headers=headers,
json={
"invoice_id": invoice_id,
"payment_tx_hash": receipt['transactionHash'].hex(),
}
)
if proof_response.status_code != 200:
raise Exception(f"Activation failed: {proof_response.text}")
return proof_response.json()["qr_code"]
# Example usage
qr_code = activate_esim(region="EU", duration_days=7)
print(f"eSIM QR code: {qr_code}")
Key points:
- The facilitator validates the
X-World-Capabilityheader against the World contract before issuing the 402 challenge. - If the nullifier hash is invalid, expired, or spending limit exceeded, you get a
403 Forbiddeninstead of402. - The agent's wallet holds only enough USDC for immediate payments. If compromised, the attacker is still bounded by the capability's spending limit.
Step 4: Validate the eSIM activation
After the agent receives the QR code, you can scan it with an eSIM-compatible device or programmatically validate it:
import re
def validate_lpa_qr(qr_code: str) -> bool:
"""Check if QR code matches LPA:1$ format (GSMA SGP.22 spec)."""
pattern = r"^LPA:1\$[A-Za-z0-9._-]+\$[A-Za-z0-9._-]+$"
return bool(re.match(pattern, qr_code))
if validate_lpa_qr(qr_code):
print("✓ Valid eSIM QR code")
else:
print("✗ Invalid QR format")
The QR code is single-use. If the agent tries to activate the same QR twice, the carrier rejects it (not our facilitator — this is GSMA spec behavior).
Gas costs and latency breakdown
From our June 2026 production metrics (840 activations with World capability tokens):
| Step | Median latency | P99 latency | Gas cost (Polygon) |
|---|---|---|---|
| World contract read (validate capability) | 0.4s | 1.1s | 0 (read-only) |
| HTTP 402 challenge generation | 0.2s | 0.6s | N/A |
| USDC transfer (agent → facilitator) | 2.1s | 8.3s | 0.003 MATIC (~$0.002) |
| eSIM activation API call (carrier backend) | 6.8s | 18.7s | N/A |
| QR code delivery | 0.1s | 0.3s | N/A |
| Total (P50) | 11.2s | 24.6s | $0.002 |
The carrier backend (step 4) dominates latency. If you need sub-10s activations, contact us about priority routing (adds $0.40/activation).
Handling capability expiry
When the capability token expires, the facilitator returns 403 Forbidden with:
{
"error": "capability_expired",
"expired_at": "2026-07-15T14:23:01Z",
"renewal_url": "https://world.org/renew?nullifier=0xabc..."
}
Your agent should:
- Log the expiry.
- Notify the human (email, Slack, etc.).
- Pause eSIM requests until a new capability is injected.
Do NOT auto-renew capabilities without human confirmation. That defeats the proof-of-humanity safeguard.
Security considerations
Nullifier hash uniqueness: Each World ID generates one nullifier hash per scope. If you create two capabilities with scope="esim-payments", they share the same nullifier but have separate spending limits. Our facilitator tracks spend per nullifier + capability ID pair.
Capability token rotation: Treat capability tokens like API keys. Rotate every 7-30 days. Shorter = more secure, longer = less human intervention.
Agent wallet isolation: The agent's Polygon wallet should hold only the USDC needed for immediate payments. Fund it in 10 USDC increments, not 1,000 USDC upfront.
Replay attacks: The World contract includes a nonce in the capability signature. Our facilitator validates the nonce server-side. You can't reuse a capability token across different facilitator instances (if you're running a multi-agent system, each agent needs its own token).
When NOT to use World AgentKit
World ID adds 2-3 seconds of latency. If your agent needs sub-5s eSIM activation (e.g., real-time failover for IoT swarms), direct x402 without World verification is faster. Trade-off: you lose proof-of-humanity.
World ID also requires Orb verification, which is available in 48 countries as of July 2026. If your users are in regions without Orb access, fall back to standard x402 with wallet-only authentication. See our use-cases page for alternative patterns.
Next steps
You've gated an agent's eSIM payments behind World ID verification. To go further:
- Multi-agent coordination: Create one capability per agent, each with isolated spending limits. Our architecture docs show the coordinator-worker pattern.
- Spend analytics: World's nullifier hash is stable per human. Query our facilitator's
/v1/analytics?nullifier=0x...endpoint to see per-human agent spend (requires your API key). - Testnet testing: Use World's staging environment (
app_staging_...) and Polygon Mumbai testnet before prod. Testnet USDC is free from the faucet.
Questions? File an issue on our GitHub or email dev@esimx402.com. We respond to technical queries within 4 hours (median, measured across 240 tickets in Q2 2026).