DEV Community

Cover image for Your 402 response says two things. Which one does the client read?
Kaven C
Kaven C

Posted on

Your 402 response says two things. Which one does the client read?

A catalog operator messaged us last week: every call to our paid endpoint was failing on their side. Our monitoring was green. Our own test client settled payments against the same endpoint that morning. Both of us were right, and that is the interesting part.

The shape of a 402

When an agent calls a paid endpoint without paying, the server answers with HTTP 402 and tells the caller what it would accept: the amount, the asset, the network, the address to pay, a timeout, and a little extra data the signing scheme needs. In x402 that offer is sent twice. Once as the JSON body of the response, and once base64-encoded in a response header, so a client that never looks at bodies can still read the terms.

Two copies of the same thing is fine as long as they are the same thing.

How they drifted

Our body and our header were built in two places. The body came from the object the rest of the server used: the one with the fee breakdown, the discovery extensions, the per-chain details. The header was projected from an earlier version of that object by a separate function. When we added a field to the body a few weeks ago, the header projection did not pick it up. One field, one copy.

Our tests read the body, because that is what our reference client does. The catalog's client read the header, because that is what the spec says a minimal client may do. It built its payment from the header, the payment did not match what our verifier expected from the body, and every call came back as a failed settlement. From their side we were broken. From ours, nothing had changed.

The fix is boring, which is the point

Build the offer once, at the moment the response is written, and derive both the body and the header from that single object. No second function anywhere should know what the offer looks like. If the header is an encoding of the body, make it literally that: the same bytes, encoded.

Then add the test you wish you had. Point a real client, not your own helper, at the live endpoint in dry-run mode. Decode the header, parse the body, and assert that the two are identical. Then let the client build a payment from the header alone and reach the step where it would sign. If that needs a wallet with funds, give it a few cents on one chain. It is the cheapest alarm you will ever buy.

That equality check is the whole article. If you serve 402s and have not written it, it will fail on the day you least expect.

The other thing we found while looking

Once you log every 402 you issue, you notice how few of them turn into payments. Agents fetch the terms, go away, and never come back. Some are crawlers. Some are agents that price-check and move on. Some are clients that fail silently like the one above. We had hundreds of issued authorizations sitting in a "pending" state from months ago, and our health alert dutifully paged us about them every morning as if they were a live problem.

They were not. A never-settled authorization costs nothing and means nothing on its own. We now count only the last seven days toward the alert, report the all-time backlog beside it for context, and break the recent ones down by tool and chain so an actual outage stands out from the background hum.

If you run a facilitator or a paid endpoint, I would like to know what share of your 402s settle. We see a long tail of challenges that are read and abandoned, and I suspect that ratio says more about client quality than about pricing.

What this runs

The endpoint in question is a support-tools door: agents pay a few cents per call in USDC to search a help centre, draft a support reply, or open a bounty board, on six chains, with no account. The door and its discovery document are at https://deskcrew.io/.well-known/x402 if you want to point your own client at it.

Top comments (0)