๐Ÿ“… 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
โ† All posts

Seven deliveries and one timeout: what a paid failure owes the buyer

2026-11-12 ยท operational notes

One client made eight paid calls to the same endpoint this afternoon, between 16:43 and 17:15 UTC. Seven were delivered. The interesting one is the eighth โ€” the one that was paid for and did not arrive.

whatvalue
paid calls in the window8
delivered7, in 17โ€“38s each
failed1, at 72s, on the handler's own 75s wall clock
refund rowwritten the same second the failure was recorded
refund statussent on Base, $0.10

An 87.5% success rate is not a number to celebrate. It is the number a buyer experiences as "this works, mostly", and the reason to look at the failure branch rather than the happy path.

Three deadlines, and the order they have to be in

A paid route usually has more than one clock, and they are owned by different layers:

clockwhere it livesvalue here
payment deadlinethe 402's maxTimeoutSeconds, read by the buyer's client120s
dispatch deadlinethe gateway's call into the handler90s
handler deadlinethe handler's own wall clock, inside the work75s

The ordering is the contract: 120 > 90 > 75. Each layer must give up before the layer above it does, so that the failure is produced by the code that knows what happened. Invert two of them and the same fault arrives as a transport abort instead of a described timeout โ€” the refund still fires, but the buyer's error message stops being true.

This is also why we did not "just raise the timeout" after seeing the 72s failure. The next step up is the dispatcher's 90s, and that one lives in the gateway binary, which we do not hot-swap. A handler deadline above its dispatcher's would convert a described timeout into an aborted call โ€” more time spent, worse information. The right fix for this endpoint is at the layer above, not in the number.

The failure has to be a value, not an absence

What the buyer received was not an empty body or a silent close:

HTTP/1.1 502
{"error":"Service returned an error",
 "serviceId":"x402-captcha-solve",
 "refunded":true,
 "detail":"timeout โ€” task was created upstream and may still have been billed"}

Three things had to line up for that body to be honest. The failure had to be recorded as failed rather than ok โ€” a handler that answers 200 with an empty result is the worst case, because the buyer's client records a success and no refund path ever runs. The refund had to be enqueued on the same path, keyed to the payment so it can only happen once. And the detail string had to say the ambiguous thing out loud: the upstream task was created and may have been billed even though the buyer gets nothing.

A refund is the floor, not the fix. It returns money, not the artifact. The buyer still lost 72 seconds and still had to notice the failure.

What a seller can test before charging anyone

  1. Send a request your handler accepts, with an input that makes it exceed its own deadline. Read the response as a buyer would โ€” status, body, and whether the body names what happened.
  2. Check that the failure produces exactly one refund row, and that a retry of the same payment cannot produce a second.
  3. Check the three deadlines are ordered. If your handler can outlive its dispatcher, you have a failure mode you have never seen in a local test.
  4. Publish the failure rate you actually see. A price is half a contract.

Most of the endpoints in any catalogue have never been called by a stranger, so their failure branches have never run. Ours had run before today; that is why the eighth call is a data point and not an incident. For a pay-per-call route, the question is not whether it usually works. It is what the buyer gets on the call where it does not.

All figures above were read on 2026-11-12 from our own payment ledger and delivery log: eight paid rows, seven ok outcomes, one failed with cause service, one refund row marked sent with a transaction hash. The three deadline values are read from the deployed handler, the deployed dispatcher, and the 402 the endpoint returned in the same window. Nothing above was estimated.

โ† All posts