x402 v1 vs v2: What Changed in the Payment Requirements Schema
x402 v2 replaces v1's bare network strings with CAIP-2 IDs like eip155:8453, renames maxAmountRequired to amount, and moves resource/description into extra.
If you built against x402 by reading Coinbase's CDP facilitator docs or a v1 seller middleware, you learned one shape for the 402 payment challenge. Live x402Version: 2 endpoints exist today with a different one — same protocol, same purpose, different field layout. This post is a field-by-field diff, aimed at anyone writing code that parses 402 responses directly rather than going through a maintained client SDK.
Two Real Responses, Side by Side
Here's x402Version: 1, from a live ContextIQ endpoint:
{
"x402Version": 1,
"accepts": [{
"scheme": "exact",
"network": "base-sepolia",
"maxAmountRequired": "10000",
"resource": "https://contextiq.trango-compute.com/api/v1/dns-inspector",
"description": "DNS Inspector record lookup — per call",
"mimeType": "*/*",
"payTo": "0x...",
"maxTimeoutSeconds": 300,
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"extra": { "name": "USDC", "version": "2" }
}],
"error": "Payment required"
}
And here's x402Version: 2, from a live PayAI echo test server on the same network:
{
"x402Version": 2,
"accepts": [{
"scheme": "exact",
"network": "eip155:84532",
"amount": "10000",
"payTo": "0x2a835A505d4Ea32372Cc420d2663b885cE089453",
"maxTimeoutSeconds": 300,
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"extra": {
"name": "USDC",
"version": "2",
"description": "Access to protected content on base-sepolia",
"mimeType": "application/json",
"resource": "https://x402.payai.network/api/base-sepolia/paid-content",
"outputSchema": { "input": { "type": "http", "method": "GET", "discoverable": true } }
}
}],
"error": "PAYMENT-SIGNATURE header is required"
}
Same network, same asset, same price. Genuinely different shape.
The Three Changes That Matter
| Field | v1 | v2 |
|---|---|---|
| Network identifier | Bare string — "base", "base-sepolia", "solana" | CAIP-2 identifier — "eip155:8453", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" |
| Payment amount | maxAmountRequired (top-level) | amount (top-level) |
resource / description / mimeType | Top-level fields | Nested inside extra |
scheme, payTo, asset, maxTimeoutSeconds | Same | Same, unchanged |
Everything not in that table is identical between the two versions — this isn't a protocol redesign, it's a schema revision to three specific fields.
Network Identifiers: Why CAIP-2
A bare string like "base" only works if every consumer shares the same private registry mapping names to chains — which is fine inside one facilitator's ecosystem, but doesn't scale as more chains and facilitators join. CAIP-2 (Chain Agnostic Improvement Proposal 2) is the existing standard for exactly this problem: a namespace:reference pair that's unambiguous without a shared lookup table. eip155:8453 is unambiguous to any tool that already knows EIP-155 chain IDs — no x402-specific registry required. eip155 is the namespace for EVM chains keyed by their standard chain ID; solana chains are identified by their genesis hash rather than a chain ID, since Solana doesn't have an EIP-155-style numeric identifier.
The practical effect: a v1 parser that does if (network === "base") will silently fail against "eip155:8453" — not throw, not warn, just never match. That's the single most common way a hand-rolled x402 client breaks against a v2 endpoint.
Amount: maxAmountRequired → amount
Purely a rename — the value is still an atomic-units integer string (e.g. "10000" for $0.01 at USDC's 6 decimals), same encoding, same meaning, different key. Code that reads body.accepts[0].maxAmountRequired gets undefined against a v2 response instead of an error, which is worse than a rename that fails loudly — undefined often gets treated as "amount unknown" or coerced to 0 depending on what happens downstream, rather than surfacing as a parse failure.
extra: From Optional Metadata to Required Home for Core Fields
In v1, extra is genuinely optional, scheme-specific metadata — EIP-712 domain fields for EVM's exact scheme, and not much else. In v2, extra becomes load-bearing: resource, description, and mimeType, which a v1 client could always find at the top level, now only exist inside it. A parser that treats extra as "nice-to-have metadata I can skip" will find itself missing fields it needs.
If You're Building a Seller
Pin which version you emit, and be consistent about it — don't emit x402Version: 2 in the top-level field while still using v1's flat structure for resource/description, or vice versa. If you're using a maintained middleware (Coinbase's CDP-integrated libraries, or a package like @trango/x402-middleware), it handles this for you; the risk is almost entirely in hand-rolled implementations that construct the JSON body directly.
If You're Building a Client, Validator, or Checker
Don't branch your entire parser on x402Version and assume everything else follows from that — check both locations for the fields that moved, the same way a resilient JSON API client handles an optional field that's sometimes present and sometimes not, regardless of which schema version claims to be in use. Concretely: read amount if maxAmountRequired is absent; read resource/description/mimeType from extra if they're absent at the top level; and resolve network as CAIP-2 (namespace:reference) when it contains a colon, falling back to a direct string match otherwise. That's a handful of fallback checks, not a rewrite.
Check Either Shape Against Your Own Endpoint
The x402 Inspector handles both v1 and v2 automatically — point it at an endpoint and it reads whichever shape comes back, rather than assuming one. If you're testing this yourself, PayAI runs a public x402 echo server across several networks that returns v2 challenges and instantly refunds any test payment, which is a safe way to see the real wire format without guessing from documentation.
Follow Trango Compute on LinkedIn
We post updates on new tools, context engineering patterns, and LLM cost research.