- Python 3.10+ installed
- Basic knowledge of FastAPI and async Python
- Access to an Ethereum-compatible JSON-RPC endpoint (Mainnet, testnet, or local)
- An ERC-20 wallet private key with some USDC or compatible tokens for testing
web3.py (v6.x), fastapi (v0.95+), uvicorn, and httpx installed
pip install fastapi uvicorn web3 httpx
The example demo uses local Ganache or a testnet with faucets for USDC.
Setting Up FastAPI for Micropayments
The core structure includes a FastAPI app with a middleware that intercepts requests to check for valid x402 payments. If payment is missing or insufficient, it replies with HTTP 402 Payment Required and a payment invoice.
Here’s the minimal FastAPI setup:
from fastapi import FastAPI, Request, HTTPException
from starlette.responses import JSONResponse
app = FastAPI()
@app.get("/public")
async def public_endpoint():
return {"message": "No payment needed here."}
@app.get("/protected")
async def protected_endpoint():
## Placeholder for payment validation
return {"message": "Here is your paid content."}
Nothing special so far. However, the next step is middleware that enforces agent pay-per-API-call.
Implementing the x402 Payment Middleware
x402 protocol specifies a RESTful payment pattern: clients send partial payment info in headers, and servers respond with 402 status code plus a payment request in the body.
The middleware checks the payment header, validates it against the expected cost (in token units), and conditionally allows the call or replies 402.
Example middleware:
from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware
from web3 import Web3
class X402PaymentMiddleware(BaseHTTPMiddleware):
def __init__(self, app, web3: Web3, price_per_call_wei: int, token_address: str):
super().__init__(app)
self.web3 = web3
self.price_per_call = price_per_call_wei
self.token_address = token_address
async def dispatch(self, request: Request, call_next):
## Skip non-protected endpoints
if request.url.path == "/public":
return await call_next(request)
payment_header = request.headers.get("X-402-Payment")
if not payment_header:
return self._response_with_payment_request()
## Decode and check payment obligation
try:
payment_details = self._parse_payment_header(payment_header)
except Exception:
return JSONResponse(status_code=400, content={"error": "Invalid payment header format."})
if not self._is_payment_sufficient(payment_details):
return self._response_with_payment_request()
response = await call_next(request)
return response
def _parse_payment_header(self, header_value: str) -> dict:
## Simple example: parse JSON string (in prod, better serialization or JWT)
import json
return json.loads(header_value)
def _is_payment_sufficient(self, payment: dict) -> bool:
## Validate token address and amount paid >= price
return (
payment.get("token") == self.token_address
and int(payment.get("amount", 0)) >= self.price_per_call
)
def _response_with_payment_request(self):
payment_request = {
"token": self.token_address,
"amountDue": str(self.price_per_call),
"message": "API call requires micropayment via x402"
}
return JSONResponse(status_code=402, content=payment_request)
To wire it up:
from web3 import Web3
rpc_url = "https://rpc.testnet.local"
web3 = Web3(Web3.HTTPProvider(rpc_url))
PRICE_WEI = 10 ** 15 # 0.001 USDC equivalent (adjust per token decimals)
TOKEN_ADDRESS = "0x...usdcContract" # Testnet USDC
app.add_middleware(X402PaymentMiddleware, web3=web3, price_per_call_wei=PRICE_WEI, token_address=TOKEN_ADDRESS)
This setup expects the client to send a header X-402-Payment with JSON specifying token and amount. Otherwise, it returns 402 with payment invoice info.
Creating an Agent Pay-Per-API-Call Example
Now let’s build a simple paid API route where an AI agent or a bot is billed per call.
The client could send this header:
{
"token": "0x...usdcContract",
"amount": "1000000000000000"
}
Here is a runnable example for a paid endpoint:
@app.get("/agent-data")
async def agent_data():
## Business logic behind paywall
return {"data": "Sensitive AI agent data or compute result."}
On the client side, if payment is sufficient, you get the data. Otherwise, HTTP 402 with the required amount.
I found when I wired up the agent's wallet, using session keys with spending limits minimized risk around overpaying. I recommend scoping agent wallet permissions tightly in production setups.
Handling HTTP 402 Payment Required Responses
Clients must handle 402 status by parsing the response, creating a micropayment transaction (via wallet SDK or contract call), then resubmitting the request with the payment header.
Example with httpx client in Python:
import httpx
import json
async def call_paid_api():
url = "http://localhost:8000/agent-data"
async with httpx.AsyncClient() as client:
resp = await client.get(url)
if resp.status_code == 402:
payment_req = resp.json()
print(f"Payment required: {payment_req}")
## TODO: create token payment tx with agent wallet
payment_header = json.dumps({
"token": payment_req["token"],
"amount": payment_req["amountDue"]
})
headers = {"X-402-Payment": payment_header}
resp = await client.get(url, headers=headers)
print(resp.json())
## Run with asyncio
This example glosses over signing transactions but shows the general agent micropayments flow.
Agent Micropayments with USDC Stablecoin
USDC is a popular stablecoin for micropayments due to low volatility and wide acceptance. Using USDC with x402 requires careful attention to token decimals (6 vs 18 decimals) and allowance approvals.
For example, USDC on Ethereum mainnet uses 6 decimals, so 1 USDC = 1_000_000 units.
When setting price_per_call_wei above, calculate the right token amount, e.g.:
price_usdc = 0.01 # 1 cent per call
usdc_decimals = 6
price_units = int(price_usdc * 10**usdc_decimals) # 10_000 units
In production, your middleware should verify the payment on-chain or via an MCP gateway to confirm the payment was settled. I recommend offloading this to an MCP (Model Context Protocol) server for permissionless micropayments and audit trails, as explained in the MCP server monetization tutorial.
Security Considerations and Common Pitfalls
- Unlimited approvals: Never ask agents for unlimited token approvals. Use session keys with spending limits.
- Untrusted MCP servers: Off-chain MCP gateways introduce trust; add retries and verification.
- Replay attacks: Ensure payment messages include nonces or timestamps.
- Private keys in app: Do not hardcode or expose secret keys; use environment variables or secure vaults.
- Testnet vs Mainnet: Test everything thoroughly on testnets; each chain's USDC may differ.
I’ve hit gotchas running this locally with mixed token decimals and RPC timeouts. So expect some iteration before production readiness.
Comparing x402 Python FastAPI to Node.js Express
| Feature |
Python FastAPI |
Node.js Express |
| Language |
Python |
JavaScript/TypeScript |
| Async support |
Native async/await |
Callback/Promise |
| Ecosystem |
Rich Python blockchain libs |
Large Node/NPM ecosystem |
| Middleware pattern |
BaseHTTPMiddleware |
Express middleware |
| Documentation |
Growing, less mature |
More community examples |
| Example setup |
Concise, especially for Py devs |
Extensive tutorials |
Both work well for x402 but Python suits teams building hybrid AI×crypto agent infrastructures.
Conclusion and Next Steps
Setting up a micropayment API gateway with x402 in Python FastAPI is straightforward but requires careful handling of payment validation and token specifics. This setup is ideal for agent pay-per-API-call models where USDC stablecoin payments need enforcement.
From here, you can extend by integrating on-chain payment verification, MCP servers, or agent wallet session keys to harden your payment pipeline. Also, check out our agent-payments-protocol-comparisons page for understanding alternatives and their trade-offs.
Try this setup with your AI agent endpoints, and watch for real-world issues like fee spikes or key management.
Happy building!
For further details on x402 integrations and agent payment tutorials, see: