Skip to main content
The payment-identifier extension provides an idempotency mechanism for x402 payments. Clients can include a unique payment ID with their requests, and servers can use this ID to deduplicate payment processing - ensuring that retries with the same payment ID return cached responses without re-processing payments.

Use Cases

  • Network failures: Safely retry failed requests without duplicate payments
  • Client crashes: Resume requests after restart using persisted payment IDs
  • Load balancing: Same request can hit different servers with shared cache
  • Testing: Replay requests during development without spending funds

How It Works

  1. Server advertises payment-identifier extension support in the PaymentRequired response
  2. Client generates a unique payment ID and includes it in the PaymentPayload
  3. Server caches responses keyed by payment ID (with configurable TTL)
  4. Retry requests with the same payment ID return cached responses without re-processing payment

Quickstart for Buyers (Clients)

Step 1: Generate a Payment ID

Use the generatePaymentId() utility to create a unique identifier:

Step 2: Add Payment ID to Extensions

Hook into the payment flow to add the payment ID before payload creation:

Best Practices

  1. Generate payment IDs at the logical request level, not per retry
  2. Persist payment IDs for long-running operations so they survive restarts
  3. Use descriptive prefixes (e.g., generatePaymentId("order_")) to identify payment types
  4. Don’t reuse payment IDs across different logical requests

Quickstart for Sellers (Servers)

Step 1: Advertise Extension Support

Declare the payment-identifier extension in your route configuration:
Optional vs Required:

Step 2: Cache Responses After Settlement

Store responses after successful payment settlement:

Step 3: Check Cache Before Payment

Use the onProtectedRequest hook to return cached responses and skip payment processing:

Request Binding

Servers should bind each payment ID to a normalized fingerprint of the request before returning a cached result. The fingerprint should cover the parts of the request that make the paid operation unique:
  • scheme, network, asset, amount, payTo
  • Resource path and HTTP method
  • Application-level operation or order identifier (when available)
Store the first observed fingerprint with the payment ID. Later requests with the same ID and the same fingerprint can return the cached response. Later requests with the same ID but a different fingerprint should return 409 Conflict — not a cached response or a second execution. When the same backend handles multiple paid resources, scope the storage key by tenant, merchant, route, or facilitator account to avoid cross-resource collisions.

Idempotency Behavior

Configuration Options

Cache TTL

Adjust CACHE_TTL_MS (TypeScript/Go) or CACHE_TTL_SECONDS (Python) based on your use case:
  • Short TTL (5-15 min): For time-sensitive resources
  • Long TTL (1-24 hours): For static or infrequently changing resources

Production Considerations

  1. Use Redis or similar instead of in-memory cache for distributed systems
  2. Handle cache failures gracefully - if cache is unavailable, process payment normally
  3. Bind payment IDs to request fingerprints - store the fingerprint (scheme, network, asset, amount, payTo, route, and application operation ID) alongside the payment ID and return 409 Conflict if the same ID is replayed with a different fingerprint
  4. Monitor cache hit rates to tune TTL and detect abuse

API Reference

Client Functions

generatePaymentId(prefix?)

Generates a cryptographically secure unique payment identifier.

appendPaymentIdentifierToExtensions(extensions, id?)

Adds a payment identifier to the extensions object. Only modifies extensions if the server declared support for the extension. If no payment ID is provided, one is generated automatically.

isValidPaymentId(id)

Validates a payment identifier format.

Server Functions

declarePaymentIdentifierExtension(required?)

Creates a payment-identifier extension declaration for resource servers.

extractPaymentIdentifier(paymentPayload)

Extracts the payment identifier from a payment payload.

validatePaymentIdentifier(extension)

Validates the payment identifier extension object structure and ID format.

Constants

Examples

Full working examples are available in the x402 repository: TypeScript: Python: Go:

FAQ

Q: What happens if I reuse a payment ID for a different request? A: If the request fingerprint differs from the original (different scheme, network, asset, amount, route, etc.), the server should return 409 Conflict. Don’t reuse payment IDs across different logical requests. Q: How long are payment IDs cached? A: This is configurable by the server. Typical TTLs range from 5 minutes to 24 hours depending on the use case. Q: Can I use custom payment ID formats? A: Payment IDs must be 16-128 characters, alphanumeric with hyphens and underscores allowed. Use isValidPaymentId() to validate custom IDs. Q: What if the server doesn’t support payment-identifier? A: The extension is optional. If the server doesn’t advertise support, clients can still make payments normally without idempotency.