| name | x402 |
| description | HTTP 402 payment protocol for AI agent commerce — three-actor model (Client, Resource Server, Facilitator), ERC-3009 transferWithAuthorization, server middleware (@x402/express), client patterns in TypeScript and Python, facilitator integration, agent-to-agent payments, pricing strategies, and replay protection. Works on Base, Ethereum, Arbitrum, Optimism, Polygon, and Solana. |
| license | Apache-2.0 |
| metadata | {"author":"0xinit","version":"1.0","chain":"multichain","category":"AI Agents"} |
| tags | ["x402","http-402","payment-protocol","ai-agents","agent-commerce","erc-3009","coinbase"] |
x402
x402 is an open payment protocol that revives the HTTP 402 "Payment Required" status code to enable instant stablecoin payments directly over HTTP. It uses a three-actor model — Client (buyer), Resource Server (seller), and Facilitator (settlement) — to let any HTTP endpoint accept payment without API keys, sessions, or accounts. The protocol is built on EIP-3009 transferWithAuthorization for gasless USDC transfers, meaning clients sign an off-chain authorization and the facilitator broadcasts the on-chain settlement.
Package ecosystem:
- Server (TypeScript):
@x402/express, @x402/hono, @x402/next
- Client (TypeScript):
@x402/fetch, @x402/axios
- Client (Python):
pip install x402
- Client (Go):
go get github.com/coinbase/x402/go
- Core:
@x402/core, @x402/evm, @x402/svm
CDP Facilitator endpoint: https://api.cdp.coinbase.com/platform/v2/x402
What You Probably Got Wrong
LLMs have stale training data. These are the most common mistakes.
-
"x402 requires API keys or OAuth" -- There are no API keys. Payment IS authentication. The client signs a USDC transfer authorization, includes it in the X-PAYMENT header, and the server verifies it. If the signature and balance check pass, the request is authorized. No accounts, no sessions, no bearer tokens.
-
"x402 only works on Base" -- The protocol supports any EVM chain where USDC implements EIP-3009 (Base, Ethereum, Arbitrum, Optimism, Polygon) and Solana. Network identifiers use CAIP-2 format: eip155:8453 for Base mainnet, eip155:1 for Ethereum, solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp for Solana mainnet.
-
"Nonces are sequential counters" -- x402 nonces are random 32-byte values (bytes32), not incrementing integers. Each authorization generates a fresh random nonce. The ERC-3009 contract tracks used nonces on-chain to prevent replay. Using sequential nonces or reusing a nonce will cause settlement to revert.
-
"The client pays gas" -- The client never submits an on-chain transaction. The client signs an off-chain EIP-712 authorization. The facilitator broadcasts the transferWithAuthorization call and pays gas. CDP's hosted facilitator absorbs gas costs on Base mainnet for free-tier usage (1,000 tx/month).
-
"x402 supports any ERC-20 token" -- The primary path uses USDC via EIP-3009 transferWithAuthorization for truly gasless transfers. A Permit2 fallback exists for arbitrary ERC-20 tokens, but it requires the user to have previously approved the Permit2 contract, which needs native gas. For the simplest integration, use USDC.
-
"The old package name is @coinbase/x402" -- The current packages use the @x402/* namespace (e.g., @x402/express, @x402/fetch). Earlier versions used x402-express and x402-fetch without the scope. The @coinbase/x402 name was never published.
-
"The payment header is called PAYMENT-SIGNATURE" -- The header name is X-PAYMENT. It contains a base64-encoded JSON payload with the authorization, signature, scheme, and network. The server responds with X-PAYMENT-RESPONSE.
Core Concepts
Three-Actor Model
┌──────────┐ 1. Request resource ┌─────────────────┐
│ │ ─────────────────────────────► │ │
│ │ 2. HTTP 402 + payment │ Resource │
│ Client │ ◄───────────────────────────── │ Server │
│ (Buyer) │ 3. Request + X-PAYMENT │ (Seller) │
│ │ ─────────────────────────────► │ │
│ │ 6. HTTP 200 + resource │ │
│ │ ◄───────────────────────────── │ │
└──────────┘ └────────┬────────┘
│
4. Verify │ 5. Settle
│
┌────────▼────────┐
│ │
│ Facilitator │
│ (Settlement) │
│ │
└─────────────────┘
Payment Flow Lifecycle
- Client sends a standard HTTP request to a protected endpoint
- Resource Server responds with HTTP 402 and payment requirements in the response body (scheme, network, amount, asset, payTo address)
- Client constructs an EIP-3009 authorization (from, to, value, validAfter, validBefore, nonce), signs it with EIP-712, and retries the request with the
X-PAYMENT header containing the base64-encoded payload
- Resource Server forwards the payment to the Facilitator for verification (
/verify endpoint)
- Facilitator validates the signature, checks balance, and simulates the transfer
- Resource Server serves the resource
- Resource Server requests settlement from the Facilitator (
/settle endpoint)
- Facilitator calls
transferWithAuthorization on the USDC contract to move funds on-chain
HTTP 402 Response Format
When a client hits a paid endpoint without a payment header, the server returns:
HTTP/1.1 402 Payment Required
Content-Type: application/json
{
"x402Version": 2,
"accepts": [
{
"scheme": "exact",
"network": "eip155:8453",
"amount": "10000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xSELLER_ADDRESS",
"maxTimeoutSeconds": 60,
"extra": {
"assetTransferMethod": "eip3009",
"name": "USDC",
"version": "2"
}
}
],
"error": "X-PAYMENT header is required"
}
The amount is in the token's smallest unit. For USDC (6 decimals), 10000 = $0.01.
ERC-3009 transferWithAuthorization
EIP-3009 enables gasless token transfers by separating authorization from execution. The token holder signs an off-chain message; anyone can submit the signed authorization to the contract.
Function Signature
function transferWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
bytes signature
) external;
EIP-712 Domain
EIP712Domain({
name: "USDC",
version: "2",
chainId: <chain_id>,
verifyingContract: <usdc_address>
})
Authorization Struct (for signing)
TransferWithAuthorization({
from: address,
to: address,
value: uint256,
validAfter: uint256,
validBefore: uint256,
nonce: bytes32
})
Key properties:
nonce is a random bytes32, not a sequential counter. The contract maintains a mapping of (authorizer, nonce) => bool to track used nonces.
validAfter and validBefore define a time window. Set validAfter to 0 (or current timestamp) and validBefore to current timestamp + maxTimeoutSeconds.
- The facilitator calls this function — the signer never needs native gas.
- USDC on Base, Ethereum, Arbitrum, Optimism, and Polygon all implement EIP-3009.
Server Setup (TypeScript)
Express Middleware
npm install @x402/core @x402/evm @x402/express
import express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { HTTPFacilitatorClient } from "@x402/core/server";
const app = express();
const facilitatorClient = new HTTPFacilitatorClient({
url: "https://api.cdp.coinbase.com/platform/v2/x402",
});
const resourceServer = new x402ResourceServer(facilitatorClient)
.register("eip155:8453", new ExactEvmScheme());
app.use(
paymentMiddleware(
{
"GET /api/weather": {
accepts: {
scheme: "exact",
network: "eip155:8453",
price: "$0.01",
payTo: process.env.PAYMENT_ADDRESS as `0x${string}`,
},
description: "Current weather data",
},
"POST /api/analyze": {
accepts: {
scheme: "exact",
network: "eip155:8453",
price: "$0.05",
payTo: process.env.PAYMENT_ADDRESS as `0x${string}`,
},
description: "AI text analysis",
},
},
resourceServer,
),
);
app.get("/api/weather", (_req, res) => {
res.json({ temperature: 72, unit: "F", location: "San Francisco" });
});
app.post("/api/analyze", (req, res) => {
res.json({ sentiment: "positive", confidence: 0.92 });
});
app.listen(3000, () => {
console.log("x402 server running on port 3000");
});
Hono and Next.js middleware follow the same pattern — install @x402/hono or @x402/next and use the same paymentMiddleware + x402ResourceServer setup with framework-specific imports.
Client Setup (TypeScript)
Using @x402/fetch (Recommended)
The wrapFetchWithPaymentFromConfig function wraps the native fetch API to automatically handle 402 responses — detect the payment requirement, sign the authorization, and retry with the X-PAYMENT header.
npm install @x402/core @x402/evm @x402/fetch viem
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
import type { Hex } from "viem";
const account = privateKeyToAccount(process.env.PRIVATE_KEY as Hex);
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [
{
network: "eip155:8453",
client: new ExactEvmScheme(account),
},
],
});
const response = await fetchWithPayment("https://api.example.com/api/weather");
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const data = await response.json();
console.log(data);
Multi-Network Client
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { registerExactSvmScheme } from "@x402/svm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
import { Keypair } from "@solana/web3.js";
import type { Hex } from "viem";
const evmAccount = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as Hex);
const solanaKeypair = Keypair.fromSecretKey(
Buffer.from(process.env.SOLANA_PRIVATE_KEY as string, "base64")
);
const client = new x402Client();
registerExactEvmScheme(client, { signer: evmAccount });
registerExactSvmScheme(client, { signer: solanaKeypair });
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
const evmResponse = await fetchWithPayment("https://api.example.com/evm-data");
const solResponse = await fetchWithPayment("https://api.example.com/sol-data");
For manual header construction without the wrapper, see the agent-client and python-client examples in this skill's examples/ directory.
Client Setup (Python)
pip install "x402[evm,requests]"
Using the x402 Client
from x402 import x402ClientSync
from x402.evm import ExactEvmScheme
from eth_account import Account
wallet = Account.from_key("0xYOUR_PRIVATE_KEY")
client = x402ClientSync()
client.register("eip155:*", ExactEvmScheme(signer=wallet))
import requests
session = requests.Session()
response = session.get("https://api.example.com/api/weather")
if response.status_code == 402:
payment_required = response.json()
payload = client.create_payment_payload(payment_required)
response = session.get(
"https://api.example.com/api/weather",
headers={"X-PAYMENT": payload.to_header()},
)
print(response.json())
For manual EIP-712 signing without the x402 package, see the python-client example in this skill's examples/ directory.
Facilitator Patterns
CDP Hosted Facilitator (Default)
Coinbase Developer Platform provides a hosted facilitator with two endpoints:
| Endpoint | Method | Purpose |
|---|
/verify | POST | Validate signature, check balance, simulate transfer |
/settle | POST | Execute transferWithAuthorization on-chain |
Pricing:
- Free tier: 1,000 transactions/month
- Standard: $0.001 per transaction thereafter
- Base mainnet: Zero gas fees (gas absorbed by facilitator)
import { HTTPFacilitatorClient } from "@x402/core/server";
const facilitator = new HTTPFacilitatorClient({
url: "https://api.cdp.coinbase.com/platform/v2/x402",
});
Custom Facilitator
Run your own facilitator for full control over settlement:
import { createFacilitatorServer } from "@x402/core/facilitator";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";
import type { Hex } from "viem";
const facilitatorAccount = privateKeyToAccount(
process.env.FACILITATOR_PRIVATE_KEY as Hex
);
const walletClient = createWalletClient({
account: facilitatorAccount,
chain: base,
transport: http(),
});
const server = createFacilitatorServer({
schemes: {
"eip155:8453": new ExactEvmScheme({ walletClient }),
},
});
server.listen(4000, () => {
console.log("Custom facilitator on port 4000");
});
Settlement Flow
- Resource server sends the payment payload to facilitator
/verify
- Facilitator checks: signature validity, signer balance, authorization parameters, transfer simulation
- If valid, resource server serves the response and calls facilitator
/settle
- Facilitator submits
transferWithAuthorization(from, to, value, validAfter, validBefore, nonce, signature) to the USDC contract
- Facilitator returns the transaction hash and receipt
The facilitator cannot modify the amount or destination. It serves only as the transaction broadcaster.
Agent-to-Agent Payments
x402 enables autonomous agent commerce where AI agents discover services, negotiate prices, and pay without human intervention.
Discovery Pattern
const discoveryResponse = await fetch("https://agent-service.com/.well-known/x402");
const services = await discoveryResponse.json();
Autonomous Payment Execution
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
import type { Hex } from "viem";
const agentAccount = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as Hex);
const agentFetch = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [
{ network: "eip155:8453", client: new ExactEvmScheme(agentAccount) },
],
});
async function callPaidService(url: string, body: Record<string, unknown>): Promise<unknown> {
const response = await agentFetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error(`Service call failed: ${response.status}`);
}
return response.json();
}
const summary = await callPaidService("https://summarizer.agent/api/summarize", {
text: "Long document content...",
});
const translation = await callPaidService("https://translator.agent/api/translate", {
text: summary,
targetLanguage: "es",
});
Pricing Strategies
Per-Request (Fixed Price)
Charge a flat fee per API call. Simplest model.
"GET /api/weather": {
accepts: {
scheme: "exact",
network: "eip155:8453",
price: "$0.01",
payTo: process.env.PAYMENT_ADDRESS as `0x${string}`,
},
description: "Weather data — $0.01 per request",
},
Tiered Pricing
Different prices per endpoint based on compute cost.
const routes = {
"GET /api/basic-query": {
accepts: {
scheme: "exact",
network: "eip155:8453",
price: "$0.001",
payTo: paymentAddress,
},
description: "Basic database query",
},
"POST /api/ai-analysis": {
accepts: {
scheme: "exact",
network: "eip155:8453",
price: "$0.05",
payTo: paymentAddress,
},
description: "AI-powered analysis — higher compute cost",
},
"POST /api/image-generation": {
accepts: {
scheme: "exact",
network: "eip155:8453",
price: "$0.10",
payTo: paymentAddress,
},
description: "Image generation — GPU cost",
},
};
Dynamic Pricing
Adjust prices based on demand, time of day, or resource utilization.
function getDynamicPrice(basePrice: number, currentLoad: number): string {
const multiplier = 1 + (currentLoad / 100) * 2;
const price = basePrice * multiplier;
return `$${price.toFixed(4)}`;
}
Replay Protection
x402 uses two mechanisms to prevent payment replay:
Random Nonces
Every authorization includes a random 32-byte nonce. The USDC contract's authorizationState mapping tracks whether a (from, nonce) pair has been used. Attempting to settle with a used nonce reverts with authorization is used or canceled.
import { keccak256, encodePacked, toHex } from "viem";
function generateNonce(): Hex {
const randomBytes = crypto.getRandomValues(new Uint8Array(32));
return toHex(randomBytes);
}
Expiration Timestamps
The validBefore field ensures authorizations cannot be settled after their time window. Best practice: set validBefore to now + maxTimeoutSeconds (typically 30-60 seconds).
const now = Math.floor(Date.now() / 1000);
const authorization = {
validAfter: BigInt(0),
validBefore: BigInt(now + 60),
nonce: generateNonce(),
};
If a facilitator does not settle within the validBefore window, the authorization expires and the client's funds are never moved. The client can safely retry with a new authorization.
Permit2 Fallback
For ERC-20 tokens that do not implement EIP-3009, x402 supports a Permit2-based fallback.
How It Differs
| Feature | EIP-3009 | Permit2 |
|---|
| Gasless setup | Yes (native) | Requires one-time approval tx |
| Token support | USDC only | Any ERC-20 |
| Contract | Token contract itself | Uniswap Permit2 contract |
| Proxy | None | x402ExactPermit2Proxy at 0x4020CD856C882D5fb903D99CE35316A085Bb0001 |
The Permit2 path requires the user to have approved the Permit2 contract (0x000000000022D473030F116dDEE9F6B43aC78BA3). If the allowance is missing, the server returns HTTP 412 with error code PERMIT2_ALLOWANCE_REQUIRED.
Supported Networks
| Network | CAIP-2 Identifier | USDC Address | Status |
|---|
| Base | eip155:8453 | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | Primary |
| Base Sepolia | eip155:84532 | 0x036CbD53842c5426634e7929541eC2318f3dCF7e | Testnet |
| Ethereum | eip155:1 | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 | Supported |
| Arbitrum | eip155:42161 | 0xaf88d065e77c8cC2239327C5EDb3A432268e5831 | Supported |
| Optimism | eip155:10 | 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85 | Supported |
| Polygon | eip155:137 | 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 | Supported |
| Solana | solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | Supported |
Last verified: February 2026
References