← All posts

World AgentKit x402: Proof-of-Humanity Agent Payments

Integrate World AgentKit with x402 to gate agent eSIM payments behind World ID verification. Step-by-step tutorial with Python code samples — with code, fee

2026-07-08·8 min read·eSIMx402 Team·x402 / world-agentkit / agentic-payments / tutorial / world

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, and worldcoin-python installed
  • 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:

  1. Human creates a capability token in the World AgentKit contract, scoped to "eSIM payments" and time-limited (e.g., 7 days).
  2. Human signs the token with their World ID credential (nullifier hash proves uniqueness without revealing identity).
  3. Agent receives the token via environment variable or secure config.
  4. Agent presents the token to our x402 facilitator in the X-World-Capability header.
  5. Facilitator validates the nullifier hash against World's on-chain registry, checks expiry, then issues the HTTP 402 challenge.
  6. Agent pays the USDC invoice (generated from the capability token's spending limit).
  7. 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-Capability header against the World contract before issuing the 402 challenge.
  • If the nullifier hash is invalid, expired, or spending limit exceeded, you get a 403 Forbidden instead of 402.
  • 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:

  1. Log the expiry.
  2. Notify the human (email, Slack, etc.).
  3. 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).

RELATED