We run two pay-per-call x402 endpoints (cross-DEX market data, Uniswap v4 hook risk scans). We settled four real mainnet payments through the CDP facilitator over four weeks — and the Bazaar merchant endpoint kept returning:
{"pagination":{"limit":20,"offset":0,"total":0},"resources":[]}
We blamed "async index lag" for a month. It wasn't lag. It was one character — well, five: http://.
The diagnostic that costs nothing
Before you pay for another settle hoping the index "catches up", decode your own 402 challenge:
curl -sI https://your-api.example.com/paid-route | \
grep -i payment-required | cut -d' ' -f2 | base64 -d | jq .resource.url
If that prints http://your-api... instead of https://, you have found your problem. CDP silently declines to index a resource whose resource.url isn't HTTPS. No error, no rejected status — the settle succeeds, your money moves, and the listing never appears.
Why it happens to almost everyone
You're behind a TLS-terminating proxy — Railway, Cloudflare, Render, Fly, a load balancer. TLS ends at the edge, so your Express app sees plain HTTP and req.protocol === 'http'. The x402 middleware bakes that protocol into resource.url in every 402 challenge and every settle payload.
The fix is one line, before you mount paymentMiddleware:
app.set('trust proxy', 1);
Use 1, not true. With true, Express takes the leftmost X-Forwarded-For entry — which any client can forge to dodge your rate limiter. With 1 it takes the proxy-observed entry, which can't be spoofed. We verified: 16 parallel requests with 16 forged X-Forwarded-For values still rate-limited correctly.
After deploying that one line, our very next settle indexed in under a minute. Four weeks of "lag", solved by one character of scheme.
Three more traps on the way to a listing
1. The 500-character description cap. The CDP facilitator hard-rejects any settle whose resource.description exceeds 500 chars — with a misleading schema-union error your buyer only ever sees as a silent second 402. We now assert the length at boot so it can't regress silently.
2. A price floor may exist. Our $0.001 route settled successfully and did not index in 30+ minutes. The same route at $0.01 indexed within four minutes of the next settle. Not conclusive (time passed too), but if your cheap route won't list, try a settle at ≥$0.01 before debugging anything else.
3. Probe traffic will impersonate demand. Within hours of listing, something started hitting our paid route at a machine-steady ~240 requests/hour — replaying the exact example input from our own discovery metadata. One User-Agent hash accounted for 94% of it. If you're counting 402 challenges as "interest", tag and exclude requests that replay your declared example, or your demand number is fiction.
Working examples
Both endpoints are live if you want a reference implementation to poke at:
-
https://x402.donnyautomation.com/call?pair=WBNB/USDC— cross-DEX prices, liquidity & arbitrage spread on BSC, $0.01 -
https://x402.donnyautomation.com/v4hooks?address=0x...— Uniswap v4 hook custody-risk decode on Base, $0.01 - Free, no-payment demo:
https://x402.donnyautomation.com/demo/v4hooks/0x000052423c1dB6B7ff8641b85A7eEfc7B2791888 - Or install the MCP wrapper and let your agent pay for itself:
npx bsc-dex-spread-mcp(npm:bsc-dex-spread-mcp)
Hit the 402, decode the header, and check that resource.url says https. Ours does now.
Top comments (0)