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
- Server advertises
payment-identifierextension support in thePaymentRequiredresponse - Client generates a unique payment ID and includes it in the
PaymentPayload - Server caches responses keyed by payment ID (with configurable TTL)
- Retry requests with the same payment ID return cached responses without re-processing payment
Quickstart for Buyers (Clients)
- TypeScript
- Python
Best Practices
- Generate payment IDs at the logical request level, not per retry
- Persist payment IDs for long-running operations so they survive restarts
- Use descriptive prefixes (e.g.,
order_,sub_) to identify payment types - Don’t reuse payment IDs across different logical requests
Quickstart for Sellers (Servers)
- TypeScript
- Python
Step 1: Advertise Extension Support
Declare the payment-identifier extension in your route configuration:Step 2: Implement Idempotency Cache
Use theextractPaymentIdentifier() utility to check for cached responses:Step 3: Cache Responses After Settlement
Store responses after successful payment settlement:Idempotency Behavior
| Scenario | Server Response |
|---|---|
| New payment ID | Process payment normally, cache response |
| Same payment ID (within TTL) | Return cached response, skip payment |
| Same payment ID (after TTL) | Process payment normally, update cache |
| No payment ID | Process payment normally (no caching) |
Configuration Options
Cache TTL
AdjustCACHE_TTL_MS 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
- Use Redis or similar instead of in-memory cache for distributed systems
- Handle cache failures gracefully - if cache is unavailable, process payment normally
- Consider payload hashing - for additional safety, hash the full payload and reject if same ID but different payload (409 Conflict)
- Monitor cache hit rates to tune TTL and detect abuse
API Reference
- TypeScript
- Python
Client Functions
generatePaymentId()
Generates a cryptographically secure unique payment identifier.appendPaymentIdentifierToExtensions(extensions, paymentId?)
Adds a payment identifier to the extensions object. 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(paymentPayload)
Validates the payment identifier in a payment payload.Constants
Examples
Full working examples are available in the x402 repository:Support
- GitHub: github.com/coinbase/x402
- Discord: Join #x402 channel
FAQ
Q: What happens if I reuse a payment ID for a different request? A: The server will return the cached response from the first request. 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. UseisValidPaymentId() 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.