x402 Explained: HTTP‑Native Micropayments for AI Agents (With Real Code)
By a senior engineer who’s actually tried it
Why x402 matters for autonomous agents
When an AI agent needs to call another service—say a language model, a data fetch, or a compute‑heavy function—it usually does so over HTTP. Traditional monetisation (API keys, subscription tiers, or invoicing) forces the agent to carry static credentials, manage billing cycles, or wait for human‑approved invoices. None of those fit a truly stateless agent that may spin up, make a single request, and disappear.
The x402 proposal (an extension of the HTTP 402 Payment Required status) solves this by turning payment into a first‑class HTTP concern:
- The server replies
402 Payment Requiredwith aPayheader that describes how much and in what token to pay. - The client (the agent) attaches a signed payment proof in the
Payheader of the retry request. - If the proof validates, the server returns the requested resource with a normal
200 OK.
Because the whole flow lives in HTTP headers, no new transport protocol, SDK, or language‑specific library is required—any HTTP client can participate.
The wire format in practice
A minimal Pay header looks like:
Pay: scheme="x402-version-1"
network="base"
token="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" # USDC on Base
amount="1000000" # 0.01 USDC (6 decimals)
payload="base64url(<request‑id>|<timestamp>)"
-
schemetells the client which version of the spec to follow. -
networkandtokenidentify the blockchain and ERC‑20 contract. -
amountis the smallest unit (USDC uses 6 decimals). -
payloadis opaque data the server can bind to a nonce or request ID to prevent replay attacks.
When the client retries, it adds a Pay header containing a signed version of the same fields plus a signature parameter:
Pay: scheme="x402-version-1"
network="base"
token="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
amount="1000000"
payload="base64url(<request‑id>|<timestamp>)"
signature="0xabcdef…"
The signature is an ECDSA‑secp256k1 signature over the concatenated UTF‑8 bytes of all preceding parameters (scheme, network, token, amount, payload). The server recovers the signer address and checks that it holds enough USDC (via a quick view‑call or an indexer) before serving the payload.
Honest trade‑offs
| Aspect | Benefit | Cost / Complexity |
|---|---|---|
| Statelessness | Agents need no long‑lived API keys; payment proves identity per request. | Requires the agent to manage a wallet and sign transactions for every call. |
| Latency | Only one extra round‑trip (402 → retry) if the client lacks funds or signature. | In practice, the extra TLS handshake + signature verification adds ~50‑150 ms on top of normal latency. |
| Fee volatility | Using a stablecoin (USDC) shields the agent from price swings. | You must hold USDC on Base; bridging from other chains incurs its own cost and delay. |
| Integration effort | Works with any HTTP client; no custom SDK needed. | Server side must implement the 402 flow, signature verification, and token‑balance checks (a modest amount of boilerplate). |
| Security | Cryptographic proof prevents replay; server‑side eliminates API‑key leakage. | If the agent’s private key is compromised, an attacker can spend its USDC until revoked. Key management remains a concern. |
| Ecosystem maturity | Draft spec is stable; several testnets and a few production services already use it. | Not yet a universal standard; you may need to convince upstream providers to adopt x402. |
In short, x402 trades a small amount of operational overhead (wallet management + signing) for a clean, usage‑based pricing model that fits the ephemeral nature of AI agents.
Minimal working example: agent → x402‑protected service
Below is a self‑contained Python snippet that demonstrates the full client flow. It assumes:
- You have an Ethereum‑compatible wallet (private key) with USDC on Base.
- You’re using the
eth-accountlibrary for signing. - The target service follows the spec described above.
# -------------------------------------------------
# x402 micropayment client for AI agents (Python 3.11+)
# -------------------------------------------------
import base64
import json
import time
import requests
from eth_account import Account
from eth_account.messages import encode_defunct
# ---- CONFIGURATION -------------------------------------------------
PRIVATE_KEY = "0xYOUR_PRIVATE_KEY_HERE" # keeper of USDC on Base
USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" # USDC on Base (checksum)
NETWORK = "base"
SCHEME = "x402-version-1"
# -------------------------------------------------------------------
def build_payload(request_id: str) -> str:
"""Create the base64url payload the server expects."""
raw = f"{request_id}|{int(time.time())}"
return base64.urlsafe_b64encode(raw.encode()).rstrip(b"=").decode()
def sign_payload(scheme, network, token, amount, payload_b64):
"""Sign the concatenated fields as per x402 spec."""
message = f"{scheme}{network}{token}{amount}{payload_b64}"
encoded = encode_defunct(text=message)
signed = Account.sign_message(encoded, private_key=PRIVATE_KEY)
return signed.signature.hex()
def call_agent_service(endpoint: str, request_id: str):
"""Perform the 402 flow and return the JSON response."""
url = f"https://example-x402.service{endpoint}"
headers = {"Accept": "application/json"}
# 1️⃣ First request – may trigger 402
resp = requests.get(url, headers=headers, timeout=10)
if resp.status_code != 402:
resp.raise_for_status()
return resp.json()
# 2️⃣ Parse the challenge
pay_header = resp.headers["Pay"]
challenge = dict(part.split('=', 1) for part in pay_header.split(', '))
# Strip quotes
challenge = {k: v.strip('"') for k, v in challenge.items()}
# 3️⃣ Build our signed response
payload_b64 = challenge["payload"]
amount = challenge["amount"]
signature = sign_payload(
scheme=challenge["scheme"],
network=challenge["network"],
token=challenge["token"],
amount=amount,
payload_b64=payload_b64,
)
pay_retry = (
f'scheme="{challenge["scheme"]}", '
f'network="{challenge["network"]}", '
f'token="{challenge["token"]}", '
f'amount="{amount}", '
f'payload="{payload_b64}", '
f'signature="0x{signature}"'
)
headers["Pay"] = pay_retry
# 4️⃣ Retry with payment proof
resp = requests.get(url, headers=headers, timeout=10)
resp.raise_for_status()
return resp.json()
# ---- Example usage -------------------------------------------------
if __name__ == "__main__":
# Imagine this agent needs a sentiment‑analysis model
result = call_agent_service("/sentiment?text=I%20love%20x402", request_id="agent-42")
print(json.dumps(result, indent=2))
What this code does
- Sends a plain GET. If the service isn’t paid, it replies
402. - Extracts the challenge from the
Payheader, builds a payload that includes a timestamp‑based nonce, and signs it with the agent’s private key. - Retries the request with a
Payheader containing the signature. - Returns the JSON payload once the server validates the proof.
You can drop this into any agent framework (LangChain, LlamaIndex, AutoGPT, etc.) and replace the endpoint with whatever service you need.
Server‑side sketch (Cloudflare Workers)
If you’re providing a service, the implementation is equally short. Below is a Worker that protects a /summarize endpoint:
javascript
// x402-protected Worker (CF Workers, JavaScript)
import { ethers } from "ethers";
const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // Base USDC
const MIN_AMOUNT = BigInt("1000000"); // 0.01 USDC (6 decimals)
async function handle(request) {
const url = new URL(request.url);
if (url.pathname !== "/summarize") return new Response("Not found", {status: 404});
const auth = request.headers.get("
Top comments (0)