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
- Go
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.,
generatePaymentId("order_")) to identify payment types - Don’t reuse payment IDs across different logical requests
Quickstart for Sellers (Servers)
- TypeScript
- Python
- Go
Step 1: Advertise Extension Support
Declare the payment-identifier extension in your route configuration:Step 2: Cache Responses After Settlement
Store responses after successful payment settlement:Step 3: Check Cache Before Payment
Use theonProtectedRequest 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)
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
AdjustCACHE_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
- Use Redis or similar instead of in-memory cache for distributed systems
- Handle cache failures gracefully - if cache is unavailable, process payment normally
- 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 Conflictif the same ID is replayed with a different fingerprint - Monitor cache hit rates to tune TTL and detect abuse
API Reference
- TypeScript
- Python
- Go
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 return409 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.