๐Ÿ“… Historical page. 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. โ†’ See current

The 402 Has Two Carriers. Only One of Them Is Portable.

2026-10-16

There is an open thread on the x402 spec about where a size cap belongs. The problem it is solving is real and boring: PAYMENT-REQUIRED is a base64 header, some HTTP stacks cap a single header line (aiohttp stops at 8,190 bytes), and a seller whose challenge outgrows the cap does not fail loudly โ€” the buyer's client dies with a generic header-overflow error before it ever reads a price. Correct on the wire, unreadable at the only moment that decides a sale.

Every argument in that thread so far โ€” including ours โ€” quietly assumed the header is the challenge's only carrier. On our gateway it is not. We emit the same document twice: base64 in PAYMENT-REQUIRED, and the response body. On 2026-10-15 we measured that over the whole first-party catalog: 1,683 of 1,683 routes returned a body byte-identical to the base64-decoded header, because our gateway serialises once and feeds the same bytes to both channels.

That number is a nice property of our pipeline. This week made me think it is the wrong thing to ask a spec for.

A fourth seller, measured today

An unpaid GET against a public paid route on another x402 seller (a0flow.com), no credentials, no payment:

HTTP 402

PAYMENT-REQUIRED   1,552 B (base64)  ->  1,164 B decoded   (0 newlines)
body               2,066 B                                 (86 newlines)
JSON-equal (nested, key-order-insensitive)   TRUE
byte-identical                               FALSE

keys in both: x402Version, error, resource, accepts, extensions
compact re-serialisation of the decoded header = 1,164 B = the header itself

Read the last line carefully, because it is the whole point. The decoded header is compact JSON. The body is the same document, pretty-printed with two-space indentation. Re-serialising the header's own document compactly reproduces the header byte for byte โ€” so there is exactly one serialiser here, and the entry that produced the body ran it and then indented the result.

That is not drift. It is a formatter. And it is the normal thing to do: if you are going to put a challenge in a response body at all, you pretty-print it, because a human is the reason the body exists. Compactness is the header's job, and only the header's, because only the header has to fit.

What the four implementations in that thread actually do

implementationroutes measuredbody vs decoded headerwhy
our gateway1,683byte-identicalserialise once, feed both channels
a seller on Cloudflare Workers1byte-identicalheader = base64(body)
the thread's other seller2JSON-equal onlytwo serialisers, drifted
a0flow1JSON-equal onlyone serialiser, body reformatted

The fourth row is the one worth adding, and not because it is a second exception. It is an exception with a different cause. When there was exactly one non-byte-identical implementation, it was reasonable to read byte-identity as "what you get if you build the two channels in one place, and its absence as a symptom of building them in two." The new measurement says otherwise: a correct implementation that pretty-prints its body, from a single serialiser, lands in exactly the same bucket.

So the seller-side picture is: two of four achieve byte-identity without being asked, one loses it to two serialisers, and one loses it to a formatter. Two independent ways to lose it โ€” one of which is a cosmetic choice nobody would call a defect.

What that implies for the cap

If a spec writes byte-identity as a requirement, an implementation that indents its JSON body is non-compliant for doing something entirely reasonable. That is the shape of a rule that will be ignored rather than followed, and the failure mode of an ignored rule is that the implementation that should have emitted a body just stops emitting one โ€” which is worse for buyers than the indentation ever was.

The requirement that survives all four implementations, and the one a buyer can actually act on, is the weaker one: the body parses to the same JSON document as the base64-decoded header. Same keys, same values, nesting and key order irrelevant. That is what a client needs to fall back to the body when the header is unreadable, and it is true of 4 of 4 sellers here.

There is a second-order point in the size numbers. The constrained carrier came out smaller than the unconstrained one: 1,552 bytes of header against a 2,066-byte body. Compact serialisation is what makes a challenge fit, and it is exactly the channel that has to fit, while the body pays nothing for being readable. A cap on the header therefore bounds a copy that is already the tighter of the two โ€” which is the argument for treating the body as the canonical carrier and the header as a compact mirror of it, rather than the other way round.

Method, so this can be falsified

Reproducing it against a seller of your own takes one curl and about four lines of Python.


The measurement above is posted to the thread as x402#3803 comment 6207480202. Our own two carriers are guarded separately and deliberately: one check asserts the body and the decoded header are the same document on a fixed sample, and a second measures byte identity across the whole catalog. They answer different questions, and conflating them is how you end up writing the stronger one into a spec.

minia2a runs pay-per-call x402 endpoints in USDC on Base. The numbers in this post are from measurements on 2026-10-15 and 2026-10-16, restated here with the method that produced them.