Repeat x402 purchases with X-Payment-Recovery: get the same bytes after a dropped connection
If a buyer pays for an x402 resource and the connection dies before the bytes arrive, the buyer should repeat the identical request with the identical PAYMENT-SIGNATURE and a private X-Payment-Recovery token, and the seller should return the original purchased response rather than a second charge or an error. The Red Cup seller at market.561.group implements this for its digital goods: seven priced publications and the Koios intelligence briefing, each marked with a recovery object in the public catalog. The mechanism reuses one idea, that an EIP-3009 authorization is single-use, to make a retry safe.
Why does a plain x402 retry fail?
A buyer's payment is a signed EIP-3009 authorization with a random nonce. Once the facilitator has settled it on chain, that nonce is spent. If the buyer's HTTP client times out after settlement and simply retries with the same signature, the facilitator rejects it as already used, and an unprepared seller turns that into an error. The buyer has paid and holds nothing. If instead the buyer signs a new authorization, it pays twice.
For a $0.0012 JSON lookup the buyer shrugs and pays again. For a $9.00 or $12.00 publication, or a $0.25 briefing that changes daily, losing the response is a real defect. That is the case recovery covers. It is deliberately opt-in: the seller's stateless computations "do not opt in and retain no customer responses".
How do I know an offer is recoverable?
The seller declares it in the catalog. For the Koios briefing, GET https://market.561.group/v1/catalog returns this on the offer:
"recovery": {
"method": "repeat-same-signed-request",
"requiredHeader": "X-Payment-Recovery",
"token": "32 random bytes as 64 lowercase hex characters, saved privately before payment",
"preserves": "exact-purchased-response"
}Eight offers carry it at the time of writing: seven publications from $9.00 to $12.00 and the briefing at $0.25.
What does the buyer send?
Before signing anything, the buyer generates a 32-byte random token and encodes it as 64 lowercase hex characters. It then sends:
GET /v1/koios/briefing/current HTTP/1.1
Host: market.561.group
PAYMENT-SIGNATURE: <base64 signed payload>
X-Payment-Recovery: 3f9c…(64 hex characters, random, private)The buyer saves the token together with the exact signed request, before sending it. The Red Cup purchase command writes both to a file with mode 0600 and sends only afterwards; if the process dies, --resume replays the saved request without needing the private key:
purchase-market-publication \
--url https://market.561.group/v1/koios/briefing/current \
--max-amount-atomic 250000 \
--output ./briefing.json \
--recovery-file ./briefing.purchase.json
# after an interruption:
purchase-market-publication --resume ./briefing.purchase.jsonThe --max-amount-atomic 250000 is the buyer's own ceiling in atomic USDC. The command refuses a challenge above it.
Why is the signature not enough to recover?
An EIP-3009 payment authorization is published on the chain when it settles. Anyone can read the payer, nonce and signature from the transaction. If the signature were the only credential, any observer could replay it and download the purchased content. The recovery token is the part that never leaves the buyer, so only the buyer who made the original request can retrieve the result.
The seller therefore refuses a signed request on a recoverable offer unless the token is present and well formed. We can show that on the live endpoint without paying. A garbage PAYMENT-SIGNATURE header and no token yields:
curl -s -i https://market.561.group/v1/koios/briefing/current -H 'PAYMENT-SIGNATURE: Zm9v'
# HTTP/2 400
# {"error":"recoverable purchases require a valid X-Payment-Recovery token"}while the same request with no payment header at all returns the normal 402 challenge.
How does the seller bind the retry to the original purchase?
The engine's recovery module (paid-response-recovery.mjs in the Red Cup x402 engine) computes two digests from the request:
- an identity that is the SHA-256 of
{network, asset, from, nonce}for the EIP-3009 authorization, lower-cased: the payment's natural key; and - a credential digest that is the SHA-256 of the signature, the recovery token and the accepted payment terms (
scheme,network,asset,amount,payTo): a value that changes if the signature or token changes, so a different credential reusing the same nonce is rejected.
It also digests the request itself, the method, the URL and the body bytes, without storing the body. Before settlement, an atomic conditional write reserves that authorization for exactly one request digest in the private store. The seller stores the prepared response bytes there before settlement, keyed by the identity. Raw payment signatures and submitted request bodies are not retained.
On a retry, the middleware checks for a completed purchase before it contacts the facilitator, because the facilitator would reject the spent nonce. The outcomes are these:
| Situation | Status | Meaning |
|---|---|---|
| Purchase complete | original status (200) | The original bytes, with the original PAYMENT-RESPONSE receipt, even if the publication has since changed or disappeared |
| Purchase still settling | 503 with Retry-After: 2 | Repeat the exact request shortly |
| Authorization already bound to a different request | 409 | The nonce was used for another URL, method or body |
| Authorization expired without settlement | 410 | Two independent Base providers say it never settled; the buyer may create a new authorization |
| Token missing or malformed | 400 | Refused before payment |
| Recovery store unavailable | 503 | Retry; the buyer is not charged by this path |
Note that the 410 rule is deliberately conservative. The seller asks two independent Base RPC providers and only then declares the authorization dead, because telling a buyer to sign again while the first payment is still in flight would cause a double spend.
What if the seller crashed after the payment was mined?
If the worker stops after the transfer is mined but before it saved the receipt, recovery corroborates the authorization's nonce and the exact USDC transfer in the successful receipt through two Base RPC providers and then completes the same purchase. An unavailable or conflicting chain observation never grants access. Recovery covers Base EIP-3009 authorizations valid for at most one hour and responses up to 16 MiB.
What does the buyer library do differently?
The Red Cup buyer (x402-exact-purchase-service, MIT) can form a durable signature separately from sending it: authorizeQuotedHttpPayment returns a replayable PAYMENT-SIGNATURE after checking resource, representation and term drift, and the library documents that the buyer "must durably retain it in a private original recovery record before sending." It treats the signature as sensitive for the same reason: whoever holds the signature and the token can retrieve the purchase. See Never trust the payee in a 402 challenge for the checks that run before signing.
What are the honest limits?
The seller's documentation says it plainly: the reference reservation "deduplicates concurrent retention calls within one boundary process", while the caller still owns cross-process exclusion, durable append atomicity, recovery and idempotence of the effect after settlement. This boundary "does not claim crash-exactly-once production." Recovery also does not make a delivery an acceptance: a recovered response proves the seller can return the bytes it prepared, not that the buyer read them. And recovery is for digital goods whose bytes can be stored; the stateless computation offers intentionally keep nothing.
How do I add this to my own seller?
- Mark specific routes recoverable in your own catalog and publish the header name and token format, as above.
- Store the prepared response durably before asking the facilitator to settle.
- Key the record by
{network, asset, payer, nonce}and bind it with an atomic conditional write to one request digest. - Check for a completed record before calling the facilitator.
- Return 409, 503 and 410 as in the table and never tell a buyer to sign again until chain evidence says the first authorization failed.
For the surrounding set of seller behaviors, return to what an x402 seller should do beyond the spec.