x402 icon indicating copy to clipboard operation
x402 copied to clipboard

Define idempotent retry semantics for `/verify` and `/settle`

Open winor30 opened this issue 3 months ago • 6 comments

Summary

  • /verify and /settle are POST endpoints but the spec never states how facilitators must deal with duplicate requests, so recoverable network errors can produce inconsistent retries.
  • Each PaymentPayload already carries a unique EIP-3009 nonce; using the canonical payload as the idempotency key would let facilitators return the original response instead of surfacing contract-level nonce errors.
  • Clarifying the requirement in specs/x402-specification.md and aligning the reference tooling gives integrators a predictable retry contract without inventing new protocol surface.

Background

  • specs/x402-specification.md POST /verify and POST /settle but omits expected behaviour for repeated submissions of the same payload.
  • Once settlement succeeds, the authorization nonce is consumed on-chain. Retrying the same payload after a transient failure eventually triggers an AuthorizationUsed revert in the ERC-3009 contract, which current facilitators surface as success: false.
  • Reference implementations (e.g. typescript/packages/x402/src/schemes/exact/evm/facilitator.ts) do not persist prior results, so applications cannot distinguish “already settled” from genuine failures.

Proposal

  1. Spec change
    • Update POST /verify and POST /settle to state that facilitators MUST treat the canonical paymentPayload as the idempotency key. The first submission performs validation/settlement; subsequent identical submissions return the original isValid / success payload (including transaction when available).
    • Add a normative error code (e.g. already_settled) that facilitators return when they detect the payload has already succeeded, so clients can stop retrying without triggering compensation flows.

Notes

  • Treating the signed payload as the idempotency key keeps the protocol self-contained across HTTP, MCP, and any other transport that forwards the same payload.

winor30 avatar Oct 11 '25 10:10 winor30

+1

shafu0x avatar Oct 12 '25 23:10 shafu0x

+1

junbeomlee avatar Oct 13 '25 05:10 junbeomlee

Thank you all for your input. My two cents: Idempotency for duplicate payments tied to the same request should be handled at the application level, which is responsible for bypassing /verify and /settle altogether.

Only the application has enough context and business logic to determine what constitutes a new payable request versus one that should reuse a previously executed payment.

The aim is for facilitators to remain stateless.

Let me know if you disagree

fabrice-cheng avatar Oct 13 '25 21:10 fabrice-cheng

@fabrice-cheng thank you for your comment. I understand the intent to keep facilitators stateless. it makes sense to keep the spec thin, and I agree that enforcing idempotency (e.g., via AuthorizationUsed) at the spec level might be too rigid.

That said, most major payment platforms like Stripe or PayPal do support idempotency on the platform side (Idempotency-Key, PayPal-Request-Id, etc.). In practice, I imagine many facilitators will end up needing a similar mechanism to handle retries safely.

winor30 avatar Oct 15 '25 12:10 winor30

Thanks. I see your point. This might be a worthy topic to bring to the V2 for the facilitator spec. May I suggest you post this exact comment in the V2 spec PR #446 ?

I'll also bring it internally with the team (tradeoff stateless)

fabrice-cheng avatar Oct 15 '25 15:10 fabrice-cheng

@fabrice-cheng Sounds great, thanks for considering it! I’ll post a summary comment in the V2 spec PR https://github.com/coinbase/x402/pull/446 as you suggested. Appreciate you also bringing it up internally.

winor30 avatar Oct 15 '25 20:10 winor30