x402 v2: What Changed and Why It Matters for Arbitrum Builders
**tl;dr **A full working x402 v2 implementation is available on GitHub, including a private inference demo and a reference facilitator. Clone them, run them, and use them as a base for your own ideas.
Most conversations about x402 focus on the headline: HTTP 402 finally has a job, agents can pay other agents, APIs can get paid without accounts and API keys.
Those outcomes are real. But what you actually feel as a builder is the friction.
You feel the moment a clean demo becomes a real service. The moment you need to support both a testnet and mainnet without duct tape. The moment a client retries a request. The moment settlement fails after you’ve already shipped the response. The moment you integrate a facilitator you didn’t write and need to understand what it actually supports.
That’s what x402 v2 is responding to. v1 proved the idea. v2 hardens it for production.
This post isn’t a spec summary. It’s a builder summary, grounded in working code from a private inference demo and an accompanying facilitator reference implementation.
Network Identity Is No Longer a Suggestion
In v1, a network was often treated as a friendly string like “base-sepolia” or “base”. Easy to pass around, easy to compare, easy to special-case.
That convenience doesn’t survive once you’re deploying to multiple environments.
v2 adopts CAIP-2 (Chain Agnostic Improvement Proposal 2), which uses the format “namespace:reference”. In the facilitator reference implementation, Arbitrum One and Arbitrum Sepolia are represented explicitly as eip155:42161 and eip155:421614. Anything else is treated as compatibility glue or rejected outright.
This matters beyond just cleaner identifiers. The CAIP-2 format means the protocol can support any blockchain, and potentially non-blockchain payment rails, without core changes. For Arbitrum builders, it means your integration won’t break when the ecosystem expands.
During verification, the network in the payment requirements and the network in the signed payload are normalized and compared. A mismatch is a hard failure:
This looks like a small refactor, but it matters more than it appears. Once you’re running on multiple chains, treating networks as strings instead of identifiers leads to branching logic everywhere. This approach keeps network identity out of your business logic entirely.
Infrastructure That Explains Itself
In v1, facilitators were treated as infrastructure you just trusted. If you integrated one, you assumed it did what it advertised. Capability discovery was informal at best.
v2 removes that assumption. A facilitator is now inspectable. It has to describe what protocol versions it supports, which payment kinds are available, which networks it works with, and which address actually performs settlement.
The facilitator reference implementation exposes this via GET /supported.
The extensions array is where v2’s formal extensions system lives. Extensions handle optional features that operate independently of core payment mechanics, each with its own info object and JSON Schema definition. The demo doesn’t use any yet, but the capability is there for things like automatic discovery and indexing.
The private inference demo uses the /supported endpoint directly. Before attempting a v2 payment flow, it queries /supported, filters for v2 kinds, and confirms that the target Arbitrum network is supported.
This is the difference between “trust me” and “inspect me.” For you as a builder, this enables multi-facilitator setups, safer defaults, and tooling that can reason about payments without hardcoded assumptions.
Putting Payments Where HTTP Expects Them
Protocols that mix metadata into response bodies tend to break middleware and proxies in subtle ways.
v2 fixes this by going header-first, following RFC-6648 which deprecated the “X-” prefix for HTTP headers. Payment requirements now live in the PAYMENT-RESPONSE header. The response body stays application-specific:
res.json({
kinds: [...v1Kinds, ...v2Kinds],
versions: { '1': { kinds: v1Kinds }, '2': { kinds: v2Kinds } },
signingAddresses: { settlement: FACILITATOR_ADDRESS },
extensions: [],
});
This means a browser can render a human-facing HTML paywall while an agent reads machine-readable payment requirements from headers. It also makes x402 behave predictably inside Express, Next.js, proxies, and edge middleware.
v2 also cleans up the payload structure. In v1, if a server accepted three tokens, it repeated the URL, description, and content type three times. v2 extracts shared resource metadata into a single object, reducing message size and eliminating inconsistencies.
The private inference demo relies on header-first behavior directly, treating payment negotiation as protocol metadata rather than application payload.
Verification Gets Boring for a Reason
There’s a class of bugs that only shows up once retries enter the picture.
Clients retry. Proxies replay. Agents duplicate calls. Users double-click.
In a payment system, retry semantics have to be explicit.
The facilitator reference implementation performs strict verification across all dimensions that matter: scheme must match the declared scheme, network must match after normalization, token address must match configured USDC (not request input), recipient must match the facilitator settlement address, and amount must match across requirements and payload.
v2 also makes payment selection explicit. In v1, the protocol used field matching heuristics to figure out which payment option the client chose. v2 adds an “accepted” field containing the complete selected payment requirement. No guessing.
Nonce reuse is enforced as well. The implementation supports database-backed nonce tracking and includes an in-memory fallback with a warning that it’s not production safe:
const serialized = JSON.stringify(requirements);
res.setHeader('PAYMENT-RESPONSE', serialized);
res.status(402).json(requirements);
If you’ve ever debugged a double-charge issue, you know why this matters. Retries are inevitable. Idempotency has to be built in from the start.
Lifecycle Hooks: Solving the Settlement Timing Problem
Remember that friction point about settlement failing after you’ve already shipped the response? v1 had a real problem here. Business logic executed after verification but before settlement. If settlement failed, you’d already done irreversible things: file transfers, API calls, database writes.
v2 introduces lifecycle hooks that let you control exactly when your logic runs relative to the payment flow. The private inference demo uses this pattern. When usage reaches a threshold, the backend returns an explicit settlementAuthorization. The client signs exactly that authorization using EIP-712 typed data for EIP-3009:
if (usedNonces.has(nonce)) {
return { valid: false, invalidReason: 'Nonce has already been used' };
}
usedNonces.add(nonce);
logger.warn('Using in-memory nonce tracking - not production safe!', { nonce });
The client then submits the signature along with the exact authorization fields that were signed:
if (response.needsSignature && response.settlementAuthorization) {
setPendingSettlement(response.settlementAuthorization);
await handleSettlementSigning(response.settlementAuthorization);
}
There’s no inference step here. The server declares the intended payment requirement. The client signs it. The facilitator verifies it. Settlement is explicit and ordered. Your business logic runs when you decide it should.
What Changes When You Move Past v1
If you’re evaluating x402 today, v2 is about the protocol starting to look like what you would have built yourself after trying to ship v1 in production.
Network identity becomes canonical through CAIP-2 normalization. Facilitators become inspectable infrastructure through /supported. Payment negotiation moves into headers and out of application payloads. Verification becomes strict, explicit, and idempotent by default. Settlement timing becomes controllable through lifecycle hooks.
If you built on v1, you don’t need to rewrite everything. v2 maintains backward compatibility through namespace isolation. The same SDK, facilitator, and server can support both versions simultaneously. Clients specify version preference via the x402Version field, and implementations respond with matching protocol versions. What v2 does is generalize what v1 proved, turning x402 from a clever exact-payment mechanism into a chain-agnostic, extensible payment interface that can survive real workloads.
Where to Go From Here
If you want to see how all of this fits together, the fastest way is to run the code.
The private inference demo shows a full x402 v2 flow end to end, including header-based requirements, explicit authorization signing, batching, and settlement on Arbitrum. It’s designed to be read, modified, and broken on purpose.
The facilitator reference implementation shows what a v2-native facilitator actually looks like in practice. It exposes /supported, enforces CAIP-2 network identity, performs strict verification, and treats idempotency as a genuine concern rather than a footnote.
The private inference demo provided, running an x402 v2 flow where usage is accumulated and settlement is handled explicitly rather than per request.
Clone both repositories, run them locally, and trace a single request from 402 to settlement. Watch where headers are used, where assumptions are enforced, and where the protocol draws boundaries between application logic and payment logic.
Autonomous payments between agents and APIs are coming. The infrastructure is finally ready. The interesting question now is what gets built on top of it.