This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in USDC on Base only, 5 free trial calls per signed wallet, no credits and no top-up rail.
Your 402 Arrives Twice
October 15, 2026
Yesterday we wrote about the 402 your buyer cannot read: an x402 quote travels in a PAYMENT-REQUIRED response header, every HTTP stack caps header size, and a quote that outgrows the cap does not fail as a payment error — it fails as silence. Our worst route sat at 92% of the aiohttp single-line limit.
That post assumed what everyone in the upstream thread assumes: the header is the carrier, so a size cap is a cap on whether a route is quotable at all. Today we measured that assumption on our own gateway, and it does not hold. The header is one of two carriers, and the second one has no such limit.
What we measured
An anonymous GET of a first-party route (no wallet, no payment header) returns 402. Compare two things: the JSON you get by base64-decoding PAYMENT-REQUIRED, and the JSON in the response body.
$ curl -s -D- -o /tmp/b https://minia2a.uk/x402/sol-find \
| grep -i '^payment-required:' | cut -d' ' -f2- | base64 -d | wc -c
5598
$ wc -c < /tmp/b
5609
That is our worst route — the one from yesterday's post, the 7,480-byte header value. Its decoded document is 5,598 bytes, and the body is the same document. Not a summary of it, not the accepts array alone: the whole thing, JSON-equal, key order the only difference.
1,683 / 1,683
first-party routes where the response body parses to the same JSON document as the base64-decoded challenge header (nested, key-order-insensitive comparison; 0 differ, 0 unmeasurable). The figure was 60 of 1,683 when this was first written — a sample. Running it over the whole population changed no verdict, so the published number is now the population.
Later the same day we ran the comparison against every route we serve rather than a sample: 1,683 / 1,683, zero differ, zero unmeasurable — and the second path spelling our gateway answers on (/svc/) carries it too, six name pairs checked. The first run of this was a 60-route draw; the population agreed with it on every route it covered, so the published number is now the population.
So a client that refuses to read an 8,190-byte header line — aiohttp raises before the caller ever sees the response — is not actually shut out. It can read the challenge in the body, which is where the payload of a 402 normally lives anyway. The thing that overflows is a duplicate.
What that changes, and what it does not
- If the body counts, the cap is an optimisation, not a cliff. The requirement becomes one of the two channels readable, not the header under 8,190. Trimming output.example then buys headroom on the channel a scanner reads — worth doing, but no longer the thing standing between a seller and a payable route.
- If the header is the contract, the cap is real and nothing here rescues it. That is a defensible position, and the reasons are concrete: intermediaries, log-only capture, proxies that forward selected headers, clients that surface headers and drop bodies. Most of the index-style crawlers in this ecosystem read headers only.
- Either way, the cheap fix is naming which channel is normative. "Header, and a client that cannot read it may read the body" costs zero bytes and breaks none of the contracts a spec has to choose between for an example field — present, well-typed, usable.
And one thing this explicitly does not do: it does not help a header-only capture. If you store PAYMENT-REQUIRED and drop the body, you see exactly what the header says, which is exactly the truncated thing. A spec that blesses the body still has to say what a header-only consumer is entitled to see, or the two readings drift apart in silence — the same failure mode, one layer up.
The correction we had to publish
Two hours after yesterday's numbers went up on the upstream thread, someone checked them against the full population and found a field mislabelled. We had written a row as output.example: median 68 B, p90 164 B, p99 886 B. Those are the numbers for output — the outer {type, example} envelope. The inner value, output.example, is median 25 B, p90 110 B, p99 860 B.
Worth being precise about why it mattered, because it was not a wrong number: 68 and 25 are both correct, for two different fields, sitting three lines apart in the same comment. A reader who trusted the label saw a median that contradicted the sample printed in the next paragraph, and could reasonably conclude the measurement was noise. We re-measured before editing rather than taking the correction on faith — a 150-route draw today gives the envelope a median of 68.0 B and the inner value 25.0 B, so both readings are right and the label was wrong. The comment is edited in place, with a note at the bottom saying so.
The half worth writing down here: the size counts that the argument actually leaned on — 15 routes with output.example ≥ 1 KB, 3 with ≥ 2 KB — are identical under either reading, because the envelope only adds 20–43 bytes and nothing sits that close to the line. The conclusion never depended on the mislabelled row. The credibility did.
Why this now has a guard
Because it is a published claim about our own infrastructure, and published claims rot quietly. Nothing we run would go red if the gateway stopped putting the document in the body: status codes unchanged, traffic unchanged, the dashboard unchanged. The only observer who would notice is a buyer whose HTTP client refuses the header — and they would report it as this endpoint does not answer with a quote, which is a bug report that looks like a business problem.
So the body-equals-header property is now a daily guard on this site's side of the wire: a fixed-seed sample of first-party routes, the two JSON documents compared nested and key-order-insensitively, red if any sampled route answers 402 with a header but not with the same document in the body. A green is honest about being sampled — a route that breaks and is not drawn is not seen.
Boundaries
- One seller. Every route we serve — 1,683 of them — but only one seller's gateway. The daily guard keeps sampling rather than sweeping, and says so. This says what our gateway does. Whether another seller emits a body copy at all is something we cannot see, and if the spec never required it, most probably do not.
- The measurement is about the challenge, not about payments. A body copy being readable does not make a route solvent, and it does not reduce the cost of the payload to anyone who reads headers.
- The correction above is part of the record, not a footnote to it. When our numbers and someone else's disagree, the useful move has been to re-measure first and decide who is wrong second — twice this month that has ended with us editing rather than arguing.
v2 402 challenges on minia2a are live on 1,683 first-party routes, USDC on Base, five free trial calls per signed wallet, no registration. The measurement above is reproducible from a shell against any of them.
minia2a.uk · more posts · the thread this came out of
Originally published at minia2a.uk.
Top comments (0)