10 Idempotency Patterns for APIs That Process Payments, Orders and Repeated Requests
Ten idempotency patterns that stop retries creating duplicate payments and orders: idempotency keys, natural keys, upserts, event IDs, state checks and more.
How do you stop repeated requests creating duplicates?
Make every operation that changes money or records safe to repeat. The main tools are client-supplied idempotency keys on create requests, unique natural keys and database constraints, upserts and PUT semantics for updates, event IDs for webhooks and messages, state checks before transitions, and outbox records so side effects happen once. Store the result of the first request and return it to retries, reject reuse of a key with different data, and handle the case where the original request is still in progress.
Duplicates happen because networks fail in the middle: the client times out, does not know whether the payment went through, and tries again. Users double-tap buttons, queues redeliver messages and webhooks arrive twice. This list is for developers and technical leads building APIs, checkouts and integrations. For payment-specific failure handling, see ecommerce payment failure handling.
Patterns for create requests
1. Client-supplied idempotency keys. The client generates a unique key (a random UUID, not personal data) for each logical operation and sends it with the request, usually in an Idempotency-Key header. The server stores the key with the result and returns that same result for any retry. Stripe's documentation describes saving the status code and body of the first request made with a key, including 500 errors, for its v1 API.
2. Reject key reuse with different data. If a retry arrives with the same key but different parameters, it is a bug, not a retry. Stripe compares parameters and returns an error; the IETF draft suggests 422 Unprocessable Content for this case.
3. Handle requests still in progress. A retry can arrive while the first request is still running. Lock on the key and return a conflict response (the IETF draft suggests 409) or wait, rather than starting a second operation.
4. Natural keys and unique constraints. Where the business has a natural unique reference, such as an order number from the storefront or an invoice number from the ERP, enforce uniqueness in the database. Even if application checks fail under concurrency, the constraint stops the duplicate.
Patterns for updates and state
5. PUT and upserts for updates. Setting a value ("status = shipped") is naturally idempotent; incrementing it ("stock = stock - 1") is not. Prefer set-style operations, or make increments conditional on a version or reference.
6. State-transition checks. Before acting, check the current state: only capture a payment that is authorised, only ship an order that is paid. A repeated "capture" on an already-captured payment then becomes a no-op that returns the current state.
7. Optimistic concurrency with versions. Include a version number or ETag with updates and reject updates based on stale data. This prevents a retried or delayed update from overwriting newer changes.
Patterns for events and side effects
8. Event and message IDs for consumers. Webhooks and queue messages can be delivered more than once. Record processed event IDs and skip duplicates; Stripe, Shopify and Razorpay all document duplicate deliveries. See webhook use cases.
9. The outbox pattern for side effects. When an operation must update the database and send an email or call another system, write the side effect to an outbox table in the same transaction, and have a worker deliver it once and mark it done. This avoids sending twice or not at all when something fails between the two steps.
10. Idempotent calls to third parties. When your system calls a payment provider or other API, pass your own idempotency key or unique reference so your retries are safe on their side too. Most payment APIs support this.
Building checkout, payments or order APIs?
ZSpace Labs builds APIs and integrations with idempotency, retries and reconciliation designed in. See our web application development service.
Pattern summary
| Pattern | Protects against | Implementation note |
|---|---|---|
| 1. Idempotency keys | Client retries after timeouts | Store key, request hash and response |
| 2. Reject mismatched reuse | Client bugs reusing keys | Compare parameters; return an error |
| 3. In-progress handling | Concurrent retries | Lock per key; conflict response |
| 4. Natural keys and constraints | Duplicates under concurrency | Unique constraint in the database |
| 5. PUT and upserts | Repeated updates | Set values; avoid blind increments |
| 6. State checks | Repeated transitions | Return current state for no-ops |
| 7. Versions and ETags | Stale or delayed updates | Reject updates on old versions |
| 8. Event IDs | Duplicate webhooks and messages | Processed-events table |
| 9. Outbox | Double or missing side effects | Same-transaction outbox plus worker |
| 10. Keys to third parties | Duplicates at providers | Pass your reference downstream |
Common mistakes
- Generating the idempotency key on the server, which makes it useless for client retries.
- Using personal data such as an email address as the key; Stripe's documentation advises against it.
- Storing keys for too short a time to cover mobile apps that retry after reconnecting.
- Saving the key only after the operation succeeds, so a crash in between allows a duplicate.
- Treating idempotency as a client concern only; the server and database must enforce it.
- Forgetting background jobs and scheduled tasks, which retry too.
Conclusion
Retries are normal; duplicates should not be. Add idempotency keys to create requests, back them with unique constraints, prefer set-style updates and state checks, deduplicate events, and use an outbox for side effects. Test by sending the same request twice, concurrently and after a simulated timeout. For the wider set of integration failures, see integration failure scenarios.
Sources
IETF RFC 9110, HTTP Semantics (idempotent methods). Stripe API reference, idempotent requests. IETF HTTPAPI working group, The Idempotency-Key HTTP Header Field (draft-ietf-httpapi-idempotency-key-header-08, October 2026). Accessed 28 October 2026.
Common questions.
An operation is idempotent if doing it several times has the same effect as doing it once. In HTTP, RFC 9110 defines PUT, DELETE and the safe methods such as GET as idempotent; POST is not, which is why APIs add idempotency keys to POST requests that create payments or orders.