DEV Community

Your x402 API settled on mainnet but never showed up in Coinbase's Bazaar? Check one character.

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":[]}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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)