Skip to main content
HomeLanding pagesHow to debug an x402 payment
LANDING PAGE

How to debug an x402 payment

8 min read·Last updated June 2, 2026

Debug x402 by symptom. A 402 reaching your agent means the client is not wrapped; verify failures usually mean you parsed the body before verifying or the network is wrong; the adapter names the cause as requirement_mismatch, settle_rejected, header_missing, and similar codes; a 503 is a transient settle issue during chain-adapter rollout. Read the code, match it to a cause, fix that.

Debug by symptom

x402 problems are easy to fix once you stop guessing and read what the failure is telling you. The protocol is small, the failures are specific, and the wire helpers and adapter name their causes precisely, so debugging is mostly matching a symptom to its cause rather than spelunking. This page is organized by the symptom you see, with the cause and the fix for each.

It assumes the x402 mechanics from how-to-implement-x402-protocol and the adapter setup from how-to-add-x402-to-my-api. The payment API product page is the broader reference. Start with the symptom that matches yours.

The agent keeps getting a 402

Symptom: your agent calls a paid endpoint and a 402 comes back to your code instead of a result. Cause: the pay side is not answering the 402. A plain fetch does not pay; it just returns the 402 as a response.

The fix is to call through the x402 client, which does the settle-and-retry for you:

TYPESCRIPT
import { createX402Client } from "@blockchain0x/x402/client";
const fetchWithPay = createX402Client({ sdk });
const res = await fetchWithPay(url); // answers the 402 internally; you get the final response

If you already use fetchWithPay and still see a 402, you are likely inspecting the response too early or adding your own 402 branch on top. Await the final response from the client and do not double-handle the 402; the client owns that step.

Verification keeps failing

Symptom: on the receive side, genuine payments or webhooks fail verification. The overwhelmingly common cause is parsing the body before verifying. The signature is an HMAC over the exact bytes that arrived, so any re-serialization breaks it.

The fix is to verify against the raw bytes. For webhooks, mount the raw-body parser and pass the Buffer in:

TYPESCRIPT
app.use(express.raw({ type: "application/json" }));
// ... webhooks.verify({ headers, rawBody: req.body, secret });  then JSON.parse afterwards

If the raw body is correct and verification still fails, check the clock: the verifier rejects deliveries more than five minutes from its own time, so a badly skewed server clock looks like a signature failure. Sync the clock or, if skew is genuinely unavoidable, use the documented tolerance override.

Wrong network or amount

Symptom: the payment is rejected as not matching, or it settles in development and fails in production. Cause: a network or amount mismatch. A sk_test_ agent quotes and settles on Base Sepolia; a sk_live_ one on Base mainnet. A payer on one network cannot satisfy a requirement on the other, and the server rejects a mismatched key with apikey.network_mismatch.

The fix is to confirm both ends agree. Check that the payer and the priced endpoint are on the same network, that the right key prefix is loaded in each environment, and that the amount the payer sends matches the quoted amountWeiUsdc in 6-decimal base units. A surprising amount of "it worked yesterday" turns out to be a test key loaded where a live key belonged.

A 503 on settle

Symptom: the settle path returns a 503. Cause: outbound settlement is in active rollout, and on a network whose chain adapter is not yet live the settle can return a transient 503. This is not a bug in your code.

The fix is to treat it as transient: retry with bounded backoff (covered in how-to-handle-failed-agent-payments), and surface it honestly rather than as a refusal. If a 503 persists on a network you expected to be live, that is the signal to raise it as an environment issue rather than keep changing your code, which is otherwise correct.

Read the error codes

The fastest debugging tool is the code the failure already gives you, so log it everywhere. Parsing a 402 can throw an X402WireError with response.not_402 (the response was not a 402), response.body_missing, or response.body_malformed (not the version-1 shape) - these mean the upstream is not speaking x402 as you expect. Parsing the payment header can throw header.missing, header.malformed, header.unknown_scheme (a scheme other than exact-usdc), or header.payload_malformed. The adapter's verification additionally reports requirement_mismatch (amount or network did not match the quote) and settle_rejected (the chain refused the settle).

Each code points at one cause, so log it on every rejection and let it route you: a header.unknown_scheme is a client sending the wrong scheme, a requirement_mismatch is a price or network disagreement, a settle_rejected is a chain-side problem. The codes turn "the payment failed" into "this exact thing failed", which is the whole game in debugging.

A quick triage flow

When you are not sure which symptom you have, a short triage gets you there fast. Ask, in order:

Did the 402 reach your agent code? If yes, the pay side is not wrapping the client; that is the unanswered-402 case. If no, the call either succeeded or failed after payment, so keep going. Did verification fail on the receive side? If yes, suspect the raw-body issue first, then the clock; read the X402WireError code to confirm which. Did the adapter report requirement_mismatch or apikey.network_mismatch? Then it is a network or amount disagreement; align the two ends. Did you get a 503? Transient settle, back off. None of those? Then it is likely an ordinary upstream error after a successful payment, which you debug like any API failure.

That sequence resolves nearly every x402 issue in a couple of minutes, because each branch lands on a named cause with a known fix. The mistake is starting from "the payment is broken" as one undifferentiated problem; start from where in the flow it broke instead.

Log for future debugging

The reason x402 is fast to debug is that the failures carry causes, but only if you logged them. Make that automatic. On the pay side, log the status and any X402WireError code from a failed call. On the receive side, log the adapter's rejection reason and the relevant identifiers (the payment request, the network, the amount) on every non-2xx. For webhooks, log the verify result code and the delivery id.

Done once, this turns the next incident from an investigation into a lookup: you open the log, read the code, and apply the matching fix from the sections above. The teams that find x402 hard to debug are usually the ones that logged "payment failed" and nothing more; the teams that find it easy logged the code. Spend the few minutes to log the causes now, and every future payment problem is a faster fix.

Common pitfalls

Three debugging traps.

Guessing instead of reading the code. The X402WireError code or adapter reason names the cause. Log it first; it usually tells you the fix.

Re-serializing the body before verifying. The single most common verify failure. Verify the raw bytes.

Treating a 503 as a code bug. It is a transient rollout condition on some networks. Retry and surface honestly before you go hunting in your own code.

What to ship today

When an x402 payment misbehaves, find the symptom above, read the X402WireError code or adapter reason it produced, and apply the matching fix: wrap the client for an unanswered 402, verify raw bytes for signature failures, align networks for mismatches, back off on a 503. Log the code on every failure so the next one is faster. For the protocol internals see how-to-implement-x402-protocol. Pricing is on the pricing page.

FAQ

Frequently asked questions.

My agent gets a 402 it cannot get past, why?

The pay side is not handling the 402. If your code calls a paid endpoint with plain fetch, a 402 is just an error to it. Wrap the call with createX402Client so the client answers the 402, settles, and retries. If you already use the client and still see a 402 reach your logic, you are probably reading the response before the client finished, so await the final response from fetchWithPay.

Verification keeps failing on the receive side, what is wrong?

Most often you parsed the body before verifying. The HMAC is over the raw bytes, so if a JSON middleware re-serialized the body the signature will not match. Mount express.raw on the route and pass the Buffer into webhooks.verify (for webhooks) or let the adapter read the raw request. The next most common cause is a clock more than five minutes off, which trips the replay window.

What do the X402WireError codes mean?

On parsing a 402: response.not_402, response.body_missing, response.body_malformed mean the upstream is not speaking x402 as expected. On parsing the payment header: header.missing, header.malformed, header.unknown_scheme (a scheme other than exact-usdc), header.payload_malformed. The adapter's verify also reports requirement_mismatch and settle_rejected. Each names the exact failure so you fix the right thing.

Why does the payment settle in dev but fail in production?

Usually a key-prefix or network mismatch. A sk_test_ key settles on Base Sepolia and sk_live_ on Base mainnet; a payer on one cannot pay a quote for the other, and the server rejects a mismatched key with apikey.network_mismatch. Confirm both ends are on the same network and the right key is loaded in each environment.

I get a 503 from settle, is something broken?

Probably not permanently. Outbound settlement is in active rollout, and on a network whose chain adapter is not yet live the settle path can return a 503. Treat it as transient: retry with backoff, and if it persists on a network you expected to work, that is the thing to raise rather than a code bug on your side.

Create your free agent wallet in 5 minutes.

First payment confirmed in under ten minutes. Free to start.