Idempotency in Payment APIs: Preventing Duplicate Transactions
Payment APIs are called over networks that retry. Clients retry on timeout; gateways retry webhooks. Without idempotency, the same logical operation can be applied more than once—leading to duplicate charges or double credits. This note covers keys, safeguards, and retry patterns.
Idempotency keys
The client sends a unique key with each request. The server stores the outcome of the first request and returns it for subsequent requests with the same key until the key expires. Keys must be client-generated (e.g. UUID v4), not derived from the request body.
POST /v1/payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{
"amount": 1000,
"currency": "USD",
"destination": "acct_xyz"
}Database-level safeguards
Store idempotency state by (key, scope) with a unique constraint. On duplicate key, return the stored response instead of re-running the operation. Use TTL (e.g. 24–72 hours) to bound storage.
Race condition handling
The first request to arrive with a given key wins. Use a single serializable transaction: check key, if missing insert and process; if present return stored result. Downstream operations (ledger, gateway) must use the same key so they are also idempotent.
Retry logic patterns
Clients should retry with the same idempotency key on timeout or 5xx. Do not retry with a new key—that would create a new operation. Return 200 with the same payload for duplicate keys so clients can treat retries as success.
Real-world failure scenarios
Timeout after gateway success: client retries with same key; server returns cached success. Duplicate webhook: deduplicate by event id and key. Partial failure: design so that either full success or full rollback is stored under the key; no half-applied state.
Book Architecture Strategy Call
Schedule a call →