基于HTTP 402状态码的原生微支付协议,为AI Agent提供无需单独billing SDK的可验证支付机制,支持无状态请求级支付验证。
TL;DR:x402 是 HTTP 的轻量扩展,允许服务器在返回响应之前请求一笔小额、可验证的支付。对于需要调用按次付费服务(LLM 推理、数据获取、工具 API)的自主 Agent 而言,它无需单独的计费 SDK 或 OAuth 流程,同时将支付语义保留在协议层。
传统 REST API 依赖带外机制——API 密钥、JWT 或订阅门户——来控制访问。这些机制对人类驱动的应用运作良好,但会给 Agent 带来摩擦,因为 Agent:
x402 的解决方案是:定义状态码 402 Payment Required(已在 RFC 7231 中预留),并用标准化的 Payment-Response header 携带加密的支付证明。客户端可以通过在后续请求中附加签名支付 blob 来满足请求,整个过程在同一个 HTTP 交互内完成。
当服务器需要支付时,返回如下响应:
HTTP/1.1 402 Payment Required
Content-Type: application/json
Payment-Request: {
"scheme": "erc20",
"network": "base",
"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
"amount": "1000000", // 0.01 USDC (6 decimals)
"payload": "<base64url‑encoded nonce>"
}
payload 是一个随机 nonce,用于防止重放攻击。Agent 必须使用私钥对 payload || amount || token || network 进行签名,并在下一次请求中返回签名。
然后 Agent 重试请求:
GET /resource HTTP/1.1
Authorization: Bearer <jwt-if-needed>
X-Payment: {
"scheme": "erc20",
"network": "base",
"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "1000000",
"signature": "0xabcd...", // ECDSA signature over the concatenated fields
"payload": "<same nonce>"
}
如果签名验证通过且 nonce 未曾使用过,服务器返回请求的资源(200 OK)并记录该 nonce 以防止重用。
以下是单个路由强制执行 x402 的自包含 Express 中间件。它使用 ethers.js 进行签名验证,并假设 Agent 持有存储在环境变量中的私钥。
// x402-middleware.js
import express from 'express';
import { ethers } from 'ethers';
import crypto from 'crypto';
const app = express();
// In‑memory nonce store (for demo; use Redis or DB in prod)
const usedNonces = new Set();
/**
* Generate a 402 response with a payment request.
*/
function paymentRequired(res, tokenAddress, amountWei) {
const nonce = crypto.randomBytes(16).toString('base64url');
const request = {
scheme: 'erc20',
network: 'base',
token: tokenAddress,
amount: amountWei.toString(),
payload: nonce,
};
res.set('Payment-Request', JSON.stringify(request));
res.status(402).json({ error: 'payment required', request });
}
/**
* Middleware that checks for a valid X-Payment header.
*/
async function x402(req, res, next, { tokenAddress, priceWei, payerAddress }) {
// If already paid, skip
if (req.headers['x-payment-verified']) return next();
const auth = req.headers['x-payment'];
if (!auth) return paymentRequired(res, tokenAddress, priceWei);
let payload;
try {
payload = JSON.parse(auth);
} catch {
return res.status(400).json({ error: 'malformed X-Payment' });
}
// Basic field checks
const required = ['scheme', 'network', 'token', 'amount', 'signature', 'payload'];
if (!required.every(k => k in payload)) {
return res.status(400).json({ error: 'missing fields in X-Payment' });
}
// Replay protection
if (usedNonces.has(payload.payload)) {
return res.status(409).json({ error: 'nonce already used' });
}
// Verify signature
const msg = ethers.utils.solidityPack(
['string', 'string', 'address', 'uint256'],
[payload.scheme, payload.network, payload.token, payload.amount]
);
const msgHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(msg + payload.payload));
const recovered = ethers.utils.recoverAddress(msgHash, payload.signature);
if (recovered.toLowerCase() !== payerAddress.toLowerCase()) {
return res.status(403).json({ error: 'invalid signature' });
}
// Record nonce and mark request as verified
usedNonces.add(payload.payload);
req.headers['x-payment-verified'] = 'true';
next();
}
/* Example usage */
const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const PRICE_USDC = ethers.utils.parseUnits('0.01', 6); // $0.01
const AGENT_PUBLIC = process.env.AGENT_ADDRESS; // e.g. 0xAbC...
app.get('/price', (req, res) => {
res.json({ usd: 0.01 });
});
app.get('/data', async (req, res) => {
// This route requires payment
await x402(req, res, () => {}, {
tokenAddress: USDC_BASE,
priceWei: PRICE_USDC,
payerAddress: AGENT_PUBLIC,
});
// If we reach here, payment succeeded
res.json({ value: Math.random() });
});
app.listen(3000, () => console.log('Listening on :3000'));
首次请求 /data 时,客户端收到带有 Payment-Request header 的 402 响应。
客户端构造签名 payload(后面会展示)并重试。
中间件验证签名、检查 nonce,仅在验证通过后才调用处理器。
如果验证失败,客户端收到带有友好 JSON body 的 4xx 错误。
Agent 需要构造 X-Payment header。使用相同的 ethers 库:
import { ethers } from 'ethers';
const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const PRICE_USDC = ethers.utils.parseUnits('0.01', 6); // 0.01 USDC
const PRIVATE_KEY = process.env.AGENT_PRIVATE_KEY; // NEVER commit this
const wallet = new ethers.Wallet(PRIVATE_KEY);
// Helper to call an endpoint that may require payment
async function fetchWithX402(url) {
let response = await fetch(url);
if (response.status !== 402) return response;
const requestHeader = response.headers.get('Payment-Request');
const request = JSON.parse(requestHeader);
const msg = ethers.utils.solidityPack(
['string', 'string', 'address', 'uint256'],
[request.scheme, request.network, request.token, request.amount]
);
const msgHash = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes(msg + request.payload)
);
const signature = await wallet.signMessage(ethers.utils.arrayify(msgHash));
const xPayment = {
scheme: request.scheme,
network: request.network,
token: request.token,
amount: request.amount,
signature,
payload: request.payload,
};
// Retry with payment header
response = await fetch(url, {
headers: { 'X-Payment': JSON.stringify(xPayment) },
});
return response;
}
// Example usage
(async () => {
const resp = await fetchWithX402('http://localhost:3000/data');
const data = await resp.json();
console.log('Paid data:', data);
})();
Agent 必须持有与服务器期望的地址(payerAddress)对应的私钥。在生产环境中,你会使用硬件签名器或托管钱包服务(例如 Coinbase Wallet、Privy)来避免在源代码中暴露密钥。
nonce 从服务器原样回显;重放攻击由服务器存储已使用的 nonce 来防止。
示例使用原生 fetch。在真实的 Agent 框架中,你会将此逻辑封装到可复用的传输适配器中。