x402 Explained: HTTP‑Native Micropayments for AI Agents (With Real Code)
Target audience: developers who are building autonomous AI agents and need a lightweight, standards‑based way to charge for API calls.
Why a New HTTP Status Code?
The HTTP spec already defines 402 Payment Required (RFC 7231 §6.5.5) but it has never been widely used. The x402 proposal repurposes 402 as a native payment trigger:
- The server returns 402 when a resource requires payment.
- The response includes a
Payment-Requiredheader that carries a machine‑readable payment request (e.g., a USDC‑on‑Base payment URL or a Lightning invoice). - The client pays the invoice, then retries the original request with a
Paymentheader containing a proof‑of‑payment (a signed transaction hash or a payment receipt). - If the proof validates, the server processes the request and returns a normal 200 (or other success) response.
Because the flow lives entirely inside HTTP, any HTTP‑client library—curl, Python requests, JavaScript fetch, or an SDK generated from OpenAPI—can be used without extra SDKs or websockets.
Core Components
| Component | Role | Typical Implementation |
|---|---|---|
| Resource Server | Holds the AI agent endpoint, detects unpaid requests, issues 402 + payment request. | Express/FastAPI middleware that checks for a valid Payment header. |
| Payment Processor | Creates and validates invoices/receipts for the chosen blockchain (USDC on Base in the example). | A lightweight service that talks to a wallet or a custodial API (e.g., Circle’s USDC API). |
| Agent Client | Issues the original request, handles 402, pays, retries with proof. | A thin wrapper around requests that automates the retry loop. |
The beauty of x402 is that the payment logic is orthogonal to the business logic of the agent. You can swap the processor for Lightning, stablecoins on other L2s, or even fiat‑gateways without touching the agent code.
Minimal Working Example
Below is a complete, runnable mini‑service that demonstrates the flow. It uses:
- Python 3.11 + FastAPI for the server.
- USDC on Base (via Circle’s programmable wallet API – you can replace this with any USDC minter).
-
eth_accountto sign and verify a simple payment receipt (the transaction hash signed by the payer’s private key).
Note: For brevity, the example skips KYC/AML checks and uses a hard‑coded wallet. In production you would integrate a proper custodial or non‑custodial service and enforce replay protection.
1. Server (server.py)
# server.py
import os
from fastapi import FastAPI, Header, HTTPException, Request, status
from fastapi.responses import JSONResponse
from eth_account import Account
from eth_account.messages import encode_defunct
import uuid
APP = FastAPI()
# In a real deployment, load these from a secure vault or env‑vars
PRIVATE_KEY = os.getenv("SERVER_PRIV_KEY", Account.create().key.hex())
SERVER_ADDRESS = Account.from_key(PRIVATE_KEY).address
# Simple in‑memory store of paid nonces to prevent replay
PAID_NONCES = set()
def verify_payment_header(payment: str) -> bool:
"""
Expected format: "<nonce>:<signature>"
Signature is an eth_sign of the nonce using the payer's private key.
We recover the address and compare it to a whitelist (here, any address).
"""
try:
nonce_hex, sig = payment.split(":")
nonce = bytes.fromhex(nonce_hex)
if nonce in PAID_NONCES:
return False # replay attack
message = encode_defunct(text=nonce_hex)
recovered = Account.recover_message(message, signature=sig)
# In practice you would check `recovered` against an allowed list.
PAID_NONCES.add(nonce)
return True
except Exception:
return False
@APP.post("/agent/infer")
async def infer(
request: Request,
payment: str = Header(None, convert_underscores=False),
):
"""
AI agent endpoint. If no valid payment is provided, return 402
with a payment request that the client must satisfy.
"""
if payment is None or not verify_payment_header(payment):
# Generate a payment request: a random nonce that the client must sign.
nonce = uuid.uuid4().bytes
nonce_hex = nonce.hex()
# In a real system you would create a USDC invoice here and return
# a URL or a payment request object. For the demo we just return the nonce.
headers = {
"Payment-Required": f"nonce={nonce_hex};amount=0.01;currency=USDC;chain=Base"
}
return JSONResponse(
status_code=status.HTTP_402_PAYMENT_REQUIRED,
content={"error": "payment required"},
headers=headers,
)
# Payment verified – run the (dummy) AI work.
payload = await request.json()
# ... actual model inference would go here ...
result = {"reply": f"You said: {payload.get('prompt', '')}"}
return JSONResponse(content=result)
if __name__ == "__main__":
import uvicorn
uvicorn.run(APP, host="0.0.0.0", port=8000)
Explanation
- The endpoint
/agent/inferfirst checks for aPaymentheader. - If missing or invalid, it returns 402 and a
Payment-Requiredheader that tells the client: “pay 0.01 USDC on Base; sign this nonce.” - Once the client signs the nonce and includes it in the header, the server validates the signature, records the nonce to prevent replay, and proceeds with the agent logic.
2. Client (client.py)
# client.py
import requests
import time
from eth_account import Account
from eth_account.messages import encode_defunct
# Replace with your own private key (never commit this!)
PRIVATE_KEY = "0xYOUR_PRIVATE_KEY_HERE"
ADDRESS = Account.from_key(PRIVATE_KEY).address
SERVER = "http://localhost:8000/agent/infer"
def make_payment_header(nonce_hex: str) -> str:
"""Sign the nonce with the agent's private key."""
message = encode_defunct(text=nonce_hex)
signed = Account.sign_message(message, private_key=PRIVATE_KEY)
return f"{nonce_hex}:{signed.signature.hex()}"
def call_agent(prompt: str):
while True:
resp = requests.post(
SERVER,
json={"prompt": prompt},
timeout=10,
)
if resp.status_code == 200:
return resp.json()
if resp.status_code != 402:
resp.raise_for_status()
# Parse the payment request from the header.
# Format: nonce=<hex>;amount=<float>;currency=USDC;chain=Base
prep = resp.headers["Payment-Required"]
parts = dict(p.split("=") for p in prep.split(";"))
nonce_hex = parts["nonce"]
amount = float(parts["amount"])
print(f"Payment required: {amount} USDC on Base. Nonce: {nonce_hex}")
# In a real integration you would:
# 1. Create a USDC payment request for `amount` on Base.
# 2. Present it to the user or your wallet and wait for confirmation.
# 3. Once confirmed, obtain the transaction hash.
#
# For this demo we *pretend* the payment succeeded and just sign the nonce.
payment_header = make_payment_header(nonce_hex)
headers = {"Payment": payment_header}
# Retry the request with the proof.
resp = requests.post(
SERVER,
json={"prompt": prompt},
headers=headers,
timeout=10,
)
if resp.status_code == 200:
return resp.json()
# If still not 200, something went wrong – surface it.
resp.raise_for_status()
if __name__ == "__main__":
output = call_agent("Explain quantum entanglement in one sentence.")
print("Agent response:", output)
Explanation
- The client loops: send request → if 402, extract the nonce from
Payment-Required. - It signs the nonce with the agent’s private key and retries with a
Paymentheader. - In a production system you would replace the “pretend payment” step with an actual USDC transfer on Base (using Circle’s wallet API, a MetaMask sign‑and‑send flow, or any other USDC minter). The signed nonce serves as proof‑of‑payment; the server validates it and credits the request.
3. Running the Demo
# 1️⃣ Install deps
pip install fastapi uvicorn eth-account requests
# 2️⃣ Start the server (in one terminal)
export SERVER_PRIV_KEY=0x1111... # any random key for the demo
python server.py
# 3️⃣ Run the client (in another terminal)
python client.py
You should see the client print “Payment required: …”, then immediately retry and receive a JSON response
Top comments (0)