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:
- 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 HTTP401with an"error"field. - Payment verification (returns
402). Once the key checks out, the platform verifies the payment for the route. Failures here come back as HTTP402with 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-Keyheader 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) returns404for an unregistered route; the payment-verify call (/verify) returns402withreason: 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/datawon't matchGET /api/dataor/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
- Read the status first.
401→ your key/signature.402→ the payment.404→ the route.502→ connectivity. - Read the field.
error(key auth) vsreason(payment) tells you the layer. - 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. - 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
- The signing details behind
invalid_signature: x402 technical deep dive - Reproduce failures safely: how to test x402 payments
- The client-side loop (nonce, proof, retry): how AI agents pay for APIs
- Pick your SDK: the nine SDKs · create an account