DEV Community

Eidon Ze
Eidon Ze

Posted on

How a GET probe makes working x402 endpoints look broken

A census of x402 endpoints is only as good as the request it sends. Send the wrong one and a perfectly healthy payment server reads as dead.

We run a read-only census of the MCP registry every Monday, and we separate the x402 payment endpoints inside it from the rest. While building the drift check for that cohort, we hit a false negative class that is worth writing down, because it inflates almost every number in this space.

what we found

Take verifi.cloud. It publishes /.well-known/x402, which is the discovery document that tells an agent where its paid resources live. It lists two resources, /verify and /verify-unlock.

Probe either one with GET and you get a 404. Cannot GET /verify. If you stop there, you record a working payment service as broken.

Probe the same URL with POST and you get a clean 402, with proper x402 v2 headers. The PAYMENT-REQUIRED header decodes to a full challenge document with an accepts array listing two assets, one USDC and one EURC, each with its network, amount, payTo address and a max timeout. That is a healthy, spec-aware endpoint.

Same URL. Same second. Two answers, and only one of them is the truth.

why it happens

An x402 challenge often depends on the request body. The price can be a function of what the caller asks for, so the server refuses to quote before it sees the body, and its routing only mounts the POST handler. That is a legitimate design, not a bug.

It is also common. the402.dev publishes a directory of 16,322 endpoints and reports 7,423 of them answer only to POST. Nearly half.

So any census that probes with a single method will misclassify a large slice of this ecosystem. The failure runs in both directions. A GET only probe calls POST only servers broken. A POST only probe misses the ones that quote on GET.

what we changed

We now try GET first, and fall back to POST when GET does not produce a challenge, and we record which method actually returned the 402. When neither does, we say so instead of guessing. Endpoints with a templated path, like /tx/:hash, cannot be probed without inventing a value, so they are marked as not probed rather than scored.

That is the difference between a number and a measurement.

the part that is actually useful to an operator

A one time census tells you where you stand. It does not tell you when you break. The thing that fails silently on a payment endpoint is drift.

A refactor, a dependency bump, a new asset added to the accepts list. The endpoint still answers, the price is still quoted, and one field quietly changed shape, which is enough for an agent to stop paying. You find out when a customer tells you, or you find out on your own schedule.

So we added two commands to mcpreflight. baseline reads your paid endpoint once and writes a small json file with the challenge shape. drift re-reads it and exits 1 when that shape has moved, printing a field level diff of exactly what changed.

npx @eidonze/mcpreflight baseline https://your-api.example/paid --method=POST
npx @eidonze/mcpreflight drift https://your-api.example/paid --method=POST
Enter fullscreen mode Exit fullscreen mode

Commit the baseline file, run the drift check on every push through the GitHub Action, and a broken challenge fails the build before it reaches a buyer.

The fingerprint is semantic. It compares the normalized accepts fields and the dialect, so whitespace, key order or a rotating signature in the raw response is never reported as drift.

what this is not

Read only. No wallet, no signing, no settlement, no payment, no retries. We never complete a payment, so nothing here proves that paying returns a resource. We also do not certify pricing, security or content quality.

why the method matters more than the number

Every census in this space reports a health percentage. Almost none of them publish which request they sent, which means the percentages are not comparable and the differences are not signal. We publish the method, the cohort definition and the per endpoint result, so anyone can re-derive the number instead of trusting it.

The weekly drift table is here and it updates every Monday.

x402 cohort and drift tracking: https://xka0085-byte.github.io/mcpreflight/x402-endpoint-inspector.html

Registry census: https://xka0085-byte.github.io/mcpreflight/state-of-mcp-2026-10.html

Source: https://github.com/xka0085-byte/mcpreflight

If you quote these figures, cite mcpreflight. The point of re-running the cohort weekly is that the number keeps moving, and a moving number needs a named source.

Top comments (1)

Collapse
 
agentsearchhq profile image
AgentSearch •

The POST-only point matches what I've run into: a lot of paid endpoints won't quote until they see the body, so a GET-only health check is basically measuring routing, not payments. One more field I'd put in the drift baseline is the order and contents of the accepts array. A client with a strict payment policy (say, USDC on one network, under a fixed max amount) will just refuse to sign when a server adds or reorders an option, and from the outside that looks exactly like a dead endpoint.