Two builders, two weeks apart, published the same result. One put an API behind an x402 paywall for nine days and logged 1,835 callers and zero payments. Another audited 16 paid endpoints in the wild and found none of them could actually be bought — the paywalls were silently broken, and the operators couldn't tell, because a broken paywall and a low-demand paywall produce the exact same dashboard: requests in, no settlements.
We run an x402 marketplace, so we read that and asked the obvious question: which one are we? We report honest zeros all the time — but had we ever proven the zero was demand and not our own paywall failing silently? We hadn't. So this week we did the thing the post-mortem prescribes: we tried to buy from ourselves with a client we didn't write. Here's exactly what we ran and what came back.
The first failure mode is the simplest: a malformed 402 body, a wrong price, a stale payment address. A client can't pay what it can't parse. So we pointed an off-the-shelf x402 client at one of our live endpoints, in dry-run mode (it reads the real payment terms and signs nothing):
npx try-x402 --dry-run --url https://minia2a.uk/x402/gas
Output, unedited:
calling https://minia2a.uk/x402/gas with no payment, to read its terms... server wants 0.5 USDC on eip155:8453 (v1 dialect) pay to: 0xf16F0882de08315B438E9f3a2Abfb2d2E5d94ECA
That's correct on all three counts that matter: the amount (0.5 USDC, matching the catalog), the network (eip155:8453, Base), and the payment address (matching our published discovery record). The token's EIP-712 domain also comes back as USD Coin — which is the correct name for USDC on Base. On some chains USDC reports its name differently, and a mismatch there recovers the buyer's signature to the wrong address. Not the case here.
The second silent killer is transport-level. MCP clients open a connection with a GET carrying Accept: text/event-stream before they ask for anything purchasable. If a paywall answers that handshake with 402 Payment Required, every compliant client walks away before it ever sees a thing to buy. The correct response to a wrong media type is 405/406, not a payment wall.
$ curl -H "Accept: text/event-stream" https://minia2a.uk/mcp
HTTP 200 — ": connected"
$ curl https://minia2a.uk/mcp
HTTP 406 — {"error":{"code":-32600,"message":"GET /mcp requires Accept: text/event-stream"}}
Negotiation completes cleanly; the payment wall only appears later, at the actual paid call. This one's healthy.
The third failure mode is the one that makes you invisible: a discovery manifest whose endpoint URLs are dead. We publish 82 endpoints in our /.well-known/x402 record. We re-checked them all — every one returns a live 402 payment offer, not a 404. A directory or agent that discovers us through the manifest lands on a working paywall, not a corpse.
It's the part we can't wish away: the demand side. Here's our own trailing data, straight from the stats endpoint:
| Signal (last 7 days) | Value |
|---|---|
| Distinct trial IPs | 844 |
| Paid calls | 11 |
| Active paying agents | 15 |
| Total on-chain volume (all-time) | 90 txns · 11.52 USDC |
And the sharpest picture of the wall: our hourly funnel has been logging 44 identities that each burned all 15 of their free trial calls, then registered zero times and paid zero times. The pattern isn't "people want to pay but can't." It's "agents consume the free allowance and leave." That is a real, boring, expensive demand problem — not a protocol bug, not a malformed response. The paywall works. The offer, on the free-to-paid boundary, doesn't convert.
That's the honest version, and it's more useful than a flattering one. A broken paywall is a two-hour fix. A demand gap is a product question: is 15 free calls too many, too few, or aimed at the wrong callers? We'd rather know which one we actually have.
If you operate any HTTP 402 / x402 endpoint and your settlement graph is flat, run these before you conclude "no demand":
npx try-x402 --dry-run --url <your-endpoint>. Does the price, network, and pay-to address match what you think you published?"USD Coin" on some chains and "USDC" on others. A wrong domain recovers the signature to a different address — valid, but belonging to nobody.GET with Accept: text/event-stream should complete, not get 402'd.Do all four with a client you didn't write, against production, the way a stranger would. Your own test suite encodes what you believe; a third-party client encodes what the protocol actually says. Where they disagree is exactly where revenue disappears silently.
We ran the check. Our zero survived it — which means it's a real product problem, and now we know it. That's a better place to build from than a flattering mystery.