2026-05-18 · 3 min read

x402 technical deep dive: the X402v1 wire contract, signing, and the request flow

This is the engineering companion to What is x402?. It covers the wire contract, how requests are signed, the exact request/response flow, the fail-closed guarantee, and how nine SDKs stay byte-identical.

HTTP 402, made concrete

402 Payment Required has sat reserved in the HTTP spec for decades because the classic web client — a browser driven by a human — doesn't transact per request. Machine clients with budgets do. x402 gives 402 a precise, signed meaning: a monetised route answers an unpaid request with a challenge that quotes an exact price; the client pays and retries; the route verifies and serves.

The frozen wire contract (X402v1)

"Frozen" means the request format and signing rules do not change between versions or languages — the bytes one implementation signs are the bytes every other implementation signs for the same inputs. That stability is what lets nine SDKs interoperate and lets you test deterministically.

Canonical string

Every authenticated call to the platform builds a canonical string of exactly six fields joined by single \n (newline, 0x0A) characters, with no leading or trailing newline:

X402v1
<HTTP-METHOD-UPPERCASE>
<request-path>
<unix-timestamp-seconds>
<nonce>
<lowercase-hex SHA-256 of the exact request body bytes>

X402v1 is the literal scheme constant. The method is uppercase ASCII. The path is exact (no host, no query). The timestamp is integer unix seconds (non-integers are rejected). The nonce is fresh per request (UUIDv4). The body hash is SHA-256 of the exact UTF-8 body bytes, lowercase hex (an empty body hashes to the well-known e3b0c442…b855).

Signature

The signature is HMAC-SHA256(key = the API secret, message = the canonical string), output as lowercase hex, 64 characters. It travels with four headers (plus Content-Type: application/json):

X-X402-Key:       <public key id>
X-X402-Timestamp: <unix seconds>
X-X402-Nonce:     <uuid>
X-X402-Signature: <64-hex hmac>

Because the body hash is in the signed string, the bytes signed must be exactly the bytes sent — SDKs build request JSON compactly and deterministically (insertion order, no incidental whitespace) so the signed and transmitted bodies match.

The request flow

A gated request with no payment proof triggers a challenge: the SDK calls POST /api/v1/challenge with {"route":"<path>"}. If the route is monetised the platform returns the price; the SDK relays that to the caller as an HTTP 402 carrying the challenge body. If the route isn't registered, a 404 is returned.

A request that carries payment proof triggers a verify: the SDK calls POST /api/v1/verify with the route, nonce, and proof. The platform answers {"allowed":true} (serve the real response) or {"allowed":false} (respond 402). The SDK itself contains no payment or pricing logic — it is a thin client that relays to these two signed endpoints and acts on the answer.

Fail-closed: the safety property

If the platform is unreachable, times out, or returns anything unexpected, the SDK returns HTTP 502 with {"error":"x402_platform_unavailable"} and never serves the protected content. It cannot accidentally give a paid response away for free during an outage. This is deliberate and identical across every SDK: paid content is served only on an explicit positive verify.

Test mode

A sandbox key (X402_ENV=sandbox) exercises the full challenge → pay → verify → allow loop with synthetic payments — same wire contract, same signatures, no real funds — so the integration can be asserted in CI. Going live is an environment switch, not a code change.

Byte-identical conformance across nine SDKs

Every official SDK ships a known-answer test pinned to a shared conformance vector. For a fixed secret, method, path, timestamp, nonce, and body, every SDK must reproduce the same 64-hex signature (c325bf…59ab5) or it is, by definition, non-conformant. That single oracle is why Express, Next.js, FastAPI, Django, Laravel, Go, Spring Boot, ASP.NET Core, and Rails behave identically on the wire — pick the SDK for your stack and the protocol behaviour is guaranteed the same.

Configuration

SDKs read X402_API_KEY and X402_SECRET (required), X402_ENV (sandbox|production), and X402_BASE_URL (the platform base; trailing slash trimmed). Two env vars and one middleware line is the whole integration surface.

Next steps