2026-07-13 · 8 min read

Debugging x402: every error code and how to fix it

One of the better things about x402 is that it fails precisely. When a request doesn't go through, you don't get a generic "something went wrong" — you get a specific HTTP status and a machine-readable reason that tells you exactly which check failed. This post is the reference: every status and reason you'll see integrating a signed x402 flow, what each one means, and how to fix it. Bookmark it for the next time a call comes back red.

Why did my x402 request fail? x402 rejects at one of two layers, each with its own status code: 401 means the request's key signature failed (auth), 402 means the payment didn't verify (unpaid, bad nonce, replay), 404 means the route isn't registered, and 502 means the SDK couldn't reach the platform and failed closed — it never served paid content. The response body names the exact reason.

The two layers, and their status codes

It helps to know where a failure happens. A signed x402 request passes two gates before it serves anything:

  1. Key authentication (returns 401). Before anything else, the platform checks that the request is signed by a valid API key — this is the X402v1 HMAC signature, not the caller's identity. Failures here come back as HTTP 401 with an "error" field.
  2. Payment verification (returns 402). Once the key checks out, the platform verifies the payment for the route. Failures here come back as HTTP 402 with a "reason" field.

Plus two edge cases: an unregistered route on the discovery endpoint returns 404, and if the SDK can't reach the platform at all, it returns 502 and serves nothing.

Every code, in one table

HTTP Field Value Layer What it means Fix
401 error invalid_signature Key auth The request signature didn't match Sign the exact X402v1 canonical string with your API secret — use the SDK, don't hand-roll
401 error unknown_key Key auth The API key isn't recognized Check X402_API_KEY; confirm the key matches X402_ENV (sandbox vs live)
401 error revoked_key Key auth The key was revoked Issue a new key in the dashboard and swap it in
401 error expired Key auth Request timestamp skew exceeded ±300s Sync your server clock (NTP)
401 error replay Key auth A signed request was reused Use a fresh nonce + timestamp per request (the SDK does this automatically)
402 reason no_such_route Payment The route isn't registered/priced Register the route + price; check the exact path and method
402 reason bad_nonce Payment The retry's nonce is missing or malformed Carry the challenge's nonce back unchanged on the retry
402 reason unpaid Payment No valid payment proof on the retry Pay the quoted amount, then retry with proof
402 reason replay Payment The payment nonce was already used Get a fresh challenge and use its new nonce
404 error no_such_route Discovery Unregistered route on the challenge endpoint Register the route (same fix as the 402 case)
500 server_error Platform A platform-side error Retry with backoff
502 error x402_platform_unavailable SDK fail-closed The SDK couldn't reach or verify with the platform Check connectivity/status; paid content is never served during an outage

The rest of this post drills into the ones you're most likely to actually hit.

invalid_signature (401) — the most common one

This means the HMAC signature on your request didn't match what the platform computed. The signature is HMAC-SHA256 over a canonical string of six fields (scheme, method, path, timestamp, nonce, body-hash), keyed by your API secret. If any input differs by a byte, the signature won't match. The usual culprits:

  • Using the key as the secret (or vice versa). The X-X402-Key header carries the public key id; the signature is keyed by the separate secret. Mixing them fails every time.
  • The signed body ≠ the sent body. The canonical string includes a SHA-256 of the exact request body bytes. If something re-serializes the JSON (adds whitespace, reorders keys) between signing and sending, the hash won't match. The SDKs build the body deterministically to avoid this — which is the main reason to use them rather than hand-rolling the signature.
  • Path or method mismatch. The canonical string uses the exact path (no host, no query) and the uppercase method. A trailing slash or a lowercase method breaks it.

Fix: let the SDK for your stack build and sign the request. If you must debug the raw signature, check it against the published conformance vector in the technical deep dive.

expired (401) — clock skew

The signed timestamp must be within ±300 seconds of the platform's clock. If your server's clock has drifted, every request looks either too old or too far in the future, and you get expired. This is almost always a container or VM whose clock isn't synced. Fix: run NTP (or your platform's time-sync) so the server clock is accurate. It's a five-minute fix that saves hours of "but my signature is correct" confusion.

unknown_key and the sandbox/live mismatch

If you get unknown_key, the platform doesn't recognize the key you signed with. The single most common cause isn't a typo — it's an environment mismatch: a sandbox key used against the live platform, or a live key against the sandbox. Check that X402_ENV (sandbox or live), X402_API_KEY, and X402_BASE_URL all belong to the same environment. Testing with a sandbox key and forgetting to swap all three on the way to production is the classic version of this bug — which is exactly why testing the full loop in sandbox first catches it before real traffic does.

no_such_route — 402 or 404, and why

You'll see no_such_route when the platform has no registered, priced route matching the request. Two things trip people up:

  • The status depends on the endpoint. The discovery call (/challenge) returns 404 for an unregistered route; the payment-verify call (/verify) returns 402 with reason: no_such_route. Same underlying condition, two codes — so don't be surprised to see both.
  • Path and method are exact. A route registered for POST /api/data won't match GET /api/data or /api/data/. Register the exact route + method + price, and confirm the request matches.

unpaid, bad_nonce, replay — the retry didn't line up

These three are all about the second request in the flow — the retry that's supposed to carry payment:

  • unpaid — the retry reached verification without valid payment proof. The agent needs to actually pay the quoted amount before retrying.
  • bad_nonce — the nonce on the retry is missing or malformed. The client must carry the nonce from the challenge back unchanged.
  • replay — the nonce was already used. Payment nonces are single-use by design (that's what stops a paid response from being claimed twice). Get a fresh challenge, which issues a new nonce, and pay against that.

If you're building the client yourself, walking the four-step client loop will show where the nonce and proof are supposed to travel.

502 x402_platform_unavailable — fail-closed, working as intended

A 502 with this body isn't your bug — it's the safety property doing its job. If the SDK can't reach the platform or gets an unexpected error verifying, it returns 502 and refuses to serve the paid content, rather than risk giving it away for free during an outage. It's identical across every SDK. If you see it persistently, check network egress from your server and the platform's status — but know that while it's happening, no paid content is leaking.

A quick debugging flow

  1. Read the status first. 401 → your key/signature. 402 → the payment. 404 → the route. 502 → connectivity.
  2. Read the field. error (key auth) vs reason (payment) tells you the layer.
  3. Check the boring things first. Clock sync (expired), environment mismatch (unknown_key), and exact path/method (no_such_route) account for most integration-time failures.
  4. Prove it in sandbox. If you can't reproduce it against the sandbox with the failure modes asserted, you've found an environment difference, which is itself the answer.

FAQ

What's the difference between a 401 and a 402 in x402? A 401 means the request's API-key signature failed (auth layer). A 402 means the key was fine but the payment didn't verify (payment layer). The response field differs too: 401 uses error, 402 uses reason.

Why do I get invalid_signature when my code looks right? Almost always because the bytes signed aren't the bytes sent (JSON re-serialization), the secret and key are swapped, or the path/method don't match the canonical string exactly. Use the SDK so the canonical string and body hash are built deterministically.

Why do I get expired errors? Your server's clock has drifted beyond the ±300-second tolerance. Sync it with NTP.

I get unknown_key but the key is correct — why? Most often an environment mismatch: a sandbox key against live (or vice versa). Confirm X402_ENV, X402_API_KEY, and X402_BASE_URL are all from the same environment.

Is a 502 x402_platform_unavailable my fault? No — it's the SDK failing closed because it couldn't reach or verify with the platform. It deliberately serves no paid content in that state. Check connectivity and platform status.

Next steps