ByAUJay
Summary: This guide shows how to embed x402—the HTTP-native “Payment Required” handshake—directly into your SDKs so developers can charge per request with a single retry and no accounts. You’ll get exact wire formats, header schemas, production-ready middleware patterns, and code examples in TypeScript and Python across EVM (USDC/EIP‑3009) and Solana.
Embedding x402 in SDKs: Making ‘Pay Required’ Developer-Friendly
Decision-makers love clean adoption curves. x402 delivers one: an API returns 402 Payment Required with machine-readable price metadata; the client signs and retries with one header; the server verifies, settles, and responds 200 OK. Your SDK should make that flow “just work” for both humans and AI agents—no new session layer, no custom billing backends.
Below is a concrete blueprint to ship x402 in your SDKs with minimal friction and maximum safety.
- What x402 adds to HTTP and why it matters to SDKs
- Precisely what to parse from a 402 and what to send back
- Client and server patterns (Axios/fetch wrappers, Express/Next/Hono middleware)
- EVM details (USDC/EIP‑3009) and Solana details (partially‑signed transactions)
- Settlement, receipts, idempotency, and observability
- Emerging extensions (upto scheme, smart wallets) and how to future‑proof
What x402 actually is (and isn’t)
- The protocol revives HTTP 402 Payment Required, a long‑reserved status, and standardizes a machine‑readable response body plus a client “X-PAYMENT” header on retry. No accounts, no OAuth—just HTTP. (docs.cdp.coinbase.com)
- The server’s 402 includes a Payment Requirements object (amount, asset, network, payTo, timeout, etc.). The client signs a payment payload and sends it as a base64‑encoded JSON in the X‑PAYMENT header. The server verifies and optionally settles (locally or via a facilitator) and returns 200 with an optional X‑PAYMENT‑RESPONSE receipt. (github.com)
- 402 itself is an HTTP standard status (reserved historically), now used consistently in this protocol. That’s why SDKs can plug into any HTTP client and become “payments‑aware,” not “provider‑locked.” (developer.mozilla.org)
For builders, Cloudflare and Coinbase have shipped reference software and docs so you don’t start from scratch: Cloudflare’s Agents SDK shows wrapFetchWithPayment and x402 middleware; Coinbase’s open‑source x402 repo defines types, headers, and facilitator endpoints. (developers.cloudflare.com)
The minimal x402 SDK contract
Your SDK should do four things, predictably, everywhere:
- Intercept 402s
- Detect HTTP 402 and parse the JSON Payment Required Response. Support multiple accepts entries (multi‑rail). (github.com)
- Decide how to pay
- Choose an accepted paymentRequirements entry based on local policy (preferred network, token, max price), then construct a Payment Payload. (github.com)
- Retry with X-PAYMENT
- Base64‑encode the Payment Payload JSON into X‑PAYMENT and re‑issue the original request unchanged (same URL/method/body). (github.com)
- Verify success and surface receipts
- Parse X‑PAYMENT‑RESPONSE (if present) for txHash/network/outcome and expose it to callers for logging, refunds, and reconciliation. (github.com)
That’s the entire developer experience: “I called the API, the SDK noticed 402, paid, retried, and returned data + a receipt.”
Exact wire formats you must support
Server → Client (on first call):
- Status: 402 Payment Required (JSON body)
- Body: Payment Required Response with fields:
- x402Version, accepts: [paymentRequirements]
- paymentRequirements fields: scheme, network, maxAmountRequired (atomic units), resource, description, mimeType, payTo, maxTimeoutSeconds, asset, extra
- Example schema is defined in the protocol README. (github.com)
Client → Server (retry):
- Header: X‑PAYMENT: base64(json(Payment Payload))
- Payment Payload fields: x402Version, scheme, network, payload (scheme‑dependent) (github.com)
Server → Client (on success):
- Status: 200 OK
- Optional header: X‑PAYMENT‑RESPONSE: base64(json({ success, txHash, networkId, error })) (github.com)
Tip: Place the parsed receipt on your SDK’s response object (e.g., response.payment.meta) so app code can audit or display it.
Client patterns: make it invisible but controllable
Wrap the platform’s default HTTP client:
- Browser/Node: fetch wrapper or Axios interceptor that:
- Replays the request once with X‑PAYMENT if 402.
- Exposes hooks like onPaymentRequired(requirements) for human‑in‑the‑loop approvals.
- Supports facilitator selection (testnet, mainnet, custom). (developers.cloudflare.com)
- Python: requests.Session subclass with a Response hook to catch 402s.
- Go: custom http.RoundTripper doing the same retry logic.
If you’re building for agents, mirror Cloudflare’s approach: create wrapFetchWithPayment(account) and paymentMiddleware() primitives that make both sides one‑liners. (developers.cloudflare.com)
TypeScript: Axios interceptor snippet (client)
import axios from "axios"; import { createWalletClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; // Skeleton utilities you provide in your SDK: import { buildPaymentHeaderFrom402, decodePaymentResponse } from "./x402-sdk"; // Wallet for EVM (USDC/EIP-3009). Use baseSepolia/base in test/prod respectively. const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); const wallet = createWalletClient({ account, transport: http() }); export function withX402(axiosInstance = axios.create()) { axiosInstance.interceptors.response.use(async (res) => res, async (error) => { const res = error.response; if (!res || res.status !== 402 || !res.data) throw error; // 1) Parse server’s Payment Required Response const { accepts, x402Version } = res.data; // 2) Choose an option (policy: prefer base, price <= $0.05) const choice = accepts.find((a: any) => a.network.includes("base")) ?? accepts[0]; // 3) Build X-PAYMENT (base64) using wallet and facilitator const xPayment = await buildPaymentHeaderFrom402({ wallet, x402Version, requirements: choice, facilitator: { url: process.env.FACILITATOR_URL! } // e.g. https://x402.org/facilitator on testnets }); // 4) Retry original request with header const cfg = { ...res.config, headers: { ...(res.config.headers || {}), "X-PAYMENT": xPayment } }; const paid = await axios.request(cfg); // 5) Parse optional X-PAYMENT-RESPONSE const receipt = decodePaymentResponse(paid.headers["x-payment-response"]); (paid as any).x402 = { receipt, requirements: choice }; return paid; }); return axiosInstance; }
This is exactly the “intercept 402 → sign → retry” loop your SDK should handle. The public header schemas and facilitator endpoints are defined in the reference spec. (github.com)
Python: requests Session adapter (client)
import base64, json, requests from typing import Optional from my_x402.evm import sign_eip3009_header # your helper from my_x402.solana import build_partial_tx # your helper class X402Session(requests.Session): def __init__(self, wallet, facilitator_url: str): super().__init__() self.wallet = wallet self.facilitator_url = facilitator_url def request(self, method, url, **kwargs): r = super().request(method, url, **kwargs) if r.status_code != 402: return r body = r.json() accepts = body["accepts"] req = self._choose(accepts) x_payment = self._build_payment_header(req, body.get("x402Version", 1)) headers = dict(kwargs.get("headers") or {}) headers["X-PAYMENT"] = x_payment paid = super().request(method, url, headers=headers, **{k:v for k,v in kwargs.items() if k!="headers"}) paid.x402 = {"receipt": self._decode_receipt(paid.headers.get("X-PAYMENT-RESPONSE")), "requirements": req} return paid def _choose(self, accepts): # Policy example: prefer solana in dev, base in prod return next((a for a in accepts if a["network"].startswith("base")), accepts[0]) def _build_payment_header(self, req, x402_version): if req["network"].startswith("solana"): payload = build_partial_tx(self.wallet, req) # base64 tx data = {"x402Version": x402_version, "scheme": req["scheme"], "network": req["network"], "payload": {"transaction": payload}} else: header = sign_eip3009_header(self.wallet, req, facilitator_url=self.facilitator_url) data = {"x402Version": x402_version, "scheme": req["scheme"], "network": req["network"], "payload": header} return base64.b64encode(json.dumps(data).encode()).decode() def _decode_receipt(self, maybe_b64: Optional[str]): if not maybe_b64: return None try: return json.loads(base64.b64decode(maybe_b64.encode()).decode()) except Exception: return None
Server patterns: middleware that “speaks 402”
On the server side, ship middleware for popular frameworks:
- Express/Hono/Next.js: paymentMiddleware(payTo, { routes… }, facilitator) that:
- For protected routes, responds 402 with a Payment Requirements body on unpaid calls.
- On paid retries, verifies via /verify (optional), performs work, settles via /settle, and returns 200 with X‑PAYMENT‑RESPONSE. (docs.cdp.coinbase.com)
Cloudflare and Coinbase examples show this pattern; your middleware should mirror the reference: define pricing per route, set network, include description/input/output schemas for discoverability, and plug into a facilitator URL for settlement. (developers.cloudflare.com)
TypeScript: Express middleware sketch (server)
import express from "express"; import { verifyPayment, settlePayment, build402Body } from "./x402-server"; const app = express(); app.use("/v1/report", async (req, res, next) => { const price = "$0.01"; // or TokenAmount in atomic units const network = process.env.NETWORK || "base"; // "base" (mainnet) or "base-sepolia" (test) const payTo = process.env.MERCHANT_ADDR!; // If no X-PAYMENT, return the 402 challenge if (!req.headers["x-payment"]) { return res.status(402).json(build402Body({ resource: `${req.protocol}://${req.get("host")}${req.originalUrl}`, price, network, payTo, description: "Daily metrics report (CSV)", mimeType: "text/csv", maxTimeoutSeconds: 60 })); } // Verify first (fast and cheap) const { isValid, invalidReason } = await verifyPayment({ paymentHeader: String(req.headers["x-payment"]), requirements: { network, payTo, price, scheme: "exact" }, facilitator: { url: process.env.FACILITATOR_URL! } }); if (!isValid) { res.setHeader("X-PAYMENT-RESPONSE", /* base64({ success:false, error: invalidReason }) */ ""); return res.status(402).json(build402Body({ resource: req.originalUrl, price, network, payTo, error: invalidReason })); } // Do the work, then settle atomically (or use x402-exec-style router to bundle fulfillments) const settlement = await settlePayment({ paymentHeader: String(req.headers["x-payment"]), requirements: { network, payTo, price, scheme: "exact" }, facilitator: { url: process.env.FACILITATOR_URL! } }); res.setHeader("X-PAYMENT-RESPONSE", settlement.base64Header); res.type("text/csv").send("date,metric\n2025-12-01,1234\n"); });
The spec defines /verify and /settle facilitator endpoints and the X‑PAYMENT‑RESPONSE header schema. Build your middleware around that contract. (github.com)
EVM details your SDK must get right (USDC/EIP‑3009)
On EVM, the concrete v1 scheme is “exact” with EIP‑3009 TransferWithAuthorization signatures (USDC by default). Why EIP‑3009? It enables gasless, single‑step transfers: the client signs off‑chain, a facilitator sponsors gas and executes on‑chain. (x402.gitbook.io)
- USDC on Base mainnet (commonly used for production) is 6 decimals at 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913. Validate you’re paying the correct asset and decimals. (docs.goldsky.com)
- Don’t hardcode EIP‑712 domain values. Read the token’s EIP‑712 name/version from the contract (e.g., via name(), version()) and include them in the requirements.extra or your signer—this is the recommended practice in x402 docs. (x402.gitbook.io)
- Security knobs:
- validBefore/validAfter windows should be short (e.g., 60–120s) to prevent replay; use a 32‑byte random nonce. Verify authorizationState/AuthorizationUsed before accepting a duplicate. (eips.ethereum.org)
- Match authorization.to with payTo, value <= maxAmountRequired, and verifyingContract == asset.
- Check chainId and network match the advertised requirements.
If you need background: L402 (Lightning’s 402+macaroons) pioneered the “pay equals auth” pattern that x402 generalizes across rails—useful as conceptual prior art for your design reviews. (docs.lightning.engineering)
Solana details your SDK must get right
On Solana, the same “exact” scheme uses a partially‑signed transaction payload instead of EIP‑3009:
- The X‑PAYMENT payload contains a base64‑encoded, partially‑signed transaction (client signs first; facilitator cosigns as fee payer). The requirements.extra commonly includes feePayer. (solana.com)
- Asset is the SPL mint (e.g., USDC mint EPjFWd… on Solana); value is in atomic units (6 decimals for USDC). Validate ATAs and decimals server‑side before settling. (docs.bridge402.tech)
- Your SDK should construct the transaction, sign with the user’s keypair, and include only the b64 transaction in the payload; the facilitator verifies and broadcasts on /settle. (solana.com)
Facilitators: verify, settle, and abstract chains
x402 lets the resource server either verify/settle locally or POST to a facilitator. The spec defines:
- POST /verify → { isValid, invalidReason }
- POST /settle → { success, txHash, networkId, error }
- GET /supported → advertised schemes/networks
This keeps your server HTTP‑native—no node management required. Your SDK should accept a facilitator object and call its endpoints accordingly. (github.com)
Coinbase’s quickstart shows testnet/mainnet toggles and metadata for discovery; Cloudflare shows how to wrap fetch in agents and pay automatically. Mirror those APIs for developer familiarity. (docs.cdp.coinbase.com)
Receipts and observability
Always surface the X‑PAYMENT‑RESPONSE header (base64 JSON). At minimum include success, txHash, and network; on failures include error codes like insufficient_funds, invalid_signature, expired, amount_mismatch. Exposing this in your SDK response object enables analytics and support playbooks. The header shape is in the reference README. (github.com)
Idempotency and retries
- Client‑side: Cache the last X‑PAYMENT for a request hash (method+URL+body) during the validBefore window; if the first retry hit a transient error, resend once with same header.
- Server‑side: Use the EIP‑3009 nonce and on‑chain AuthorizationUsed to reject duplicates safely. For Solana, reject if the same recent blockhash/nonce/tx signature is reused. (eips.ethereum.org)
Pricing strategies: exact today, “upto” emerging
- exact: The server names a price; the client pays exactly that value (in atomic units). This is the default and is what v1 SDKs must implement. (github.com)
- upto (emerging): Verify first (max), run the work, then settle the final amount up to max—great for AI token‑metered pricing. Design your SDK APIs to be scheme‑pluggable; third‑party stacks already prototype this split‑phase flow. (portal.thirdweb.com)
“Settle and execute” patterns (advanced)
Many apps need to tie payment to business logic atomically (splits, mints, or downstream calls). Track proposals like x402‑exec (SettlementRouter + hook execution) so your SDK can forward “execution hints” to facilitators when they go live; it preserves strong consistency between payment and fulfillment. (github.com)
Discovery and agent UX
Two important integrations your SDK should acknowledge:
- Bazaar (discovery): If a seller uses a facilitator like CDP, their endpoints can be auto‑listed with description/input/output schemas to help agents find and use them. Support reading these schemas so your SDK can auto‑fill tool definitions. (docs.cdp.coinbase.com)
- MCP/Agents: Wrap fetch with payment in agents (or provide withX402Client for MCP), so tools can be marked “paid” and auto‑confirmed via an onPaymentRequired callback. Cloudflare provides a working pattern you can copy. (developers.cloudflare.com)
Security checklist for SDK maintainers
- Validate everything:
- Scheme matches, network matches, asset matches, payTo matches.
- Amount <= maxAmountRequired; handle atomic units precisely (BigInt/decimal).
- Enforce time windows:
- validAfter/validBefore short windows (e.g., 30–120s) to reduce replay.
- Nonce discipline:
- 32‑byte random nonce; check authorizationState/used nonces on EVM; Solana: blockhash timeouts and unique signers. (eips.ethereum.org)
- Sign exactly what the server advertised:
- Bind resource URL and possibly a server‑generated nonce to reduce header reuse across endpoints (include in requirements.extra and authorization nonce derivation).
- Don’t hardcode USDC EIP‑712 name/version:
- Read from the contract (name(), version()) as recommended by network support docs. (x402.gitbook.io)
- Handle facilitator errors gracefully:
- Distinguish verification vs settlement failures; surface error and receipt.
Compliance and risk notes
- Facilitators can help with production‑grade compliance and operations on mainnet (API keys, rate limits, monitoring). Align your SDK defaults with supported facilitators for production vs testnets. (docs.cdp.coinbase.com)
- Sanctions/geofencing, fraud checks, and tax invoices are outside the x402 core but are often layered by facilitators—design extension points for merchant‑side policies without changing the core handshake.
End‑to‑end example: EVM “exact” signing (USDC/EIP‑3009)
Pseudocode to sign the TransferWithAuthorization payload in your SDK (keep it internal, only expose buildPaymentHeader):
import { keccak256, encodeAbiParameters } from "viem"; // or use a typed-data helper type Eip3009Auth = { from: `0x${string}`; to: `0x${string}`; value: bigint; // atomic units validAfter: bigint; // unix validBefore: bigint; // unix nonce: `0x${string}`; // 32 bytes }; export async function signEip3009USDC( wallet, // viem wallet client or ethers.js signer tokenAddress: `0x${string}`, // USDC verifyingContract domainName: string, // read via name() domainVersion: string, // read via version() (often "2") chainId: number, auth: Eip3009Auth ) { const typedData = { domain: { name: domainName, version: domainVersion, chainId, verifyingContract: tokenAddress }, types: { EIP712Domain: [ { name: "name", type: "string" }, { name: "version", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" }, ], TransferWithAuthorization: [ { name: "from", type: "address" }, { name: "to", type: "address" }, { name: "value", type: "uint256" }, { name: "validAfter", type: "uint256" }, { name: "validBefore", type: "uint256" }, { name: "nonce", type: "bytes32" }, ], }, primaryType: "TransferWithAuthorization" as const, message: auth, }; const signature = await wallet.signTypedData(typedData); return { authorization: auth, signature }; // put inside payload for X-PAYMENT }
This corresponds to the ERC‑3009 spec used by x402 “exact” on EVM. Your SDK builds a Payment Payload with this signature and base64‑encodes it into X‑PAYMENT. (eips.ethereum.org)
Testing matrix (what we run before a release)
- EVM: base‑sepolia (test), base (mainnet)
- USDC transferWithAuthorization end‑to‑end via facilitator
- TokenAmount and price string (“$0.001”) parsing parity
- ValidAfter/ValidBefore expiry handling
- Solana: devnet and mainnet
- Partial‑signed tx path; feePayer set via requirements.extra
- Rejection on wrong mint/decimals
- Interop:
- 402 → X‑PAYMENT retry on all HTTP methods (GET/POST/PUT)
- Idempotency: duplicate X‑PAYMENT rejected; receipt contains consistent error
Performance and UX tips
- Minimize round‑trips: cache a service’s latest accepts entries; if you’ve seen them before, pre‑construct a header and go straight to a paid call (the spec allows skipping the initial 402 if you already know requirements). (github.com)
- Human‑in‑the‑loop: provide a confirmPayment callback; your wrapper should pass the parsed requirements (price, network, description) and await approval—essential for consumer UIs and corporate spend controls. (developers.cloudflare.com)
- Metadata matters: encourage sellers to include description/inputSchema/outputSchema; it improves agent discoverability and tool wiring (e.g., in Bazaar). Your SDK should surface these fields. (docs.cdp.coinbase.com)
Roadmap readiness: smart wallets and beyond
- Smart accounts (EIP‑4337) are being discussed for x402; plan pluggable signing so your SDK can swap EIP‑3009 auth for a UserOperation when available (and detect gas sponsorship via requirements.extra). (github.com)
- Keep scheme‑agnostic: exact today, upto tomorrow. Don’t bake business logic into your signer; let server requirements guide the shape. (portal.thirdweb.com)
Rollout plan for your organization
- Pilot on testnets
- Use base‑sepolia or Solana devnet facilitator (public endpoints exist for quickstarts). Ship one protected route and one agent client. (docs.cdp.coinbase.com)
- Harden
- Add receipt logging, idempotency keys, and price monitors. Confirm EIP‑712 domain reads from contracts in CI. (x402.gitbook.io)
- Turn on mainnet
- Swap facilitator config to mainnet; keep the same SDK interfaces. Ensure treasury wallets and reconciliation dashboards consume X‑PAYMENT‑RESPONSE. (docs.cdp.coinbase.com)
- Extend
- Offer both EVM and Solana rails. Advertise rich metadata for discovery. Consider x402‑exec‑style hooks for atomic “pay‑and‑fulfill.” (github.com)
Bottom line
x402 makes payments an HTTP concern—simple, stateless, and machine‑readable. Your SDK is the leverage point: intercept one status code, sign one payload, retry one time, and return both data and a receipt. Copy the reference headers and flows, abstract the wallet/facilitator bits, and you’ll ship “pay‑per‑request” that developers actually enjoy using.
Further reading and references:
- MDN: HTTP 402 Payment Required. (developer.mozilla.org)
- Coinbase x402 spec (headers, schemas, facilitator API) and quickstarts. (github.com)
- Cloudflare Agents: wrapFetchWithPayment and x402 middleware for Workers/MCP. (developers.cloudflare.com)
- ERC‑3009 spec for TransferWithAuthorization (USDC). (eips.ethereum.org)
- L402/LSAT: the 402 precedent coupling payment with authentication. (docs.lightning.engineering)
Like what you're reading? Let's build together.
Get a free 30‑minute consultation with our engineering team.

