Skip to main content

API Idempotency: Why It Matters and How to Implement It

·APIScout Team
api idempotencyapi designbest practicesapi reliabilityapi architecture

API Idempotency: Why It Matters and How to Implement It

A customer clicks "Pay Now." The request times out. Did the payment go through? The customer clicks again. Without idempotency, they get charged twice. With idempotency, the second request returns the same result as the first — no duplicate charge.

Idempotency means: making the same request multiple times produces the same result as making it once.

Which HTTP Methods Are Idempotent?

MethodIdempotent?Why
GET✅ YesReading data doesn't change state
PUT✅ YesReplacing a resource with the same data = same result
DELETE✅ YesDeleting an already-deleted resource = same result
PATCH❌ NoPartial updates may produce different results (e.g., increment)
POST❌ NoCreating a resource twice = two resources

The problem: POST and PATCH are not naturally idempotent. But POST is used for the most critical operations — payments, orders, account creation.

The Idempotency Key Pattern

Stripe popularized this pattern. The client generates a unique key per operation and includes it in the request header.

Request:

POST /api/payments
Idempotency-Key: idem_abc123def456
Content-Type: application/json

{
  "amount": 4999,
  "currency": "usd",
  "customer": "cus_123"
}

Server behavior:

  1. First request: Process the payment, store the result keyed by idem_abc123def456, return the result
  2. Second request (same key): Look up the stored result, return it without reprocessing
  3. Different key: Process as a new payment

Implementation Steps

  1. Client generates a unique key — UUID v4 is the standard choice
  2. Server receives the request — check if this idempotency key exists in the store
  3. Key not found — process the request, store {key, status_code, response_body, created_at}
  4. Key found, processing — return 409 Conflict (request is still being processed)
  5. Key found, completed — return the stored response (same status code and body)
  6. Key found, different request body — return 422 (can't reuse a key for a different request)

Storage Requirements

FieldPurpose
idempotency_keyThe unique key (primary key)
request_hashHash of the request body (detect misuse)
status_codeStored HTTP response code
response_bodyStored response JSON
statusprocessing or completed
created_atWhen the key was first used
expires_atWhen to garbage collect (24-48 hours)

Key Expiration

Idempotency keys should expire after 24-48 hours. After that, the same key can be reused. This prevents unbounded storage growth while covering retry windows.

When to Implement Idempotency

Always implement for:

  • Payment processing (charges, refunds, transfers)
  • Account creation
  • Order placement
  • Any operation where duplication has financial or data consequences

Optional for:

  • Update operations (PUT is already idempotent)
  • Read operations (GET is already idempotent)
  • Delete operations (DELETE is already idempotent)
  • Operations where duplicates are harmless (sending a notification twice is annoying but not catastrophic)

How Stripe Does It

Stripe accepts Idempotency-Key header on all POST requests:

POST /v1/charges
Idempotency-Key: idem_abc123
Authorization: Bearer sk_live_...
  • Keys expire after 24 hours
  • Replayed requests return the original response
  • Using the same key with different parameters returns an error
  • Keys are scoped to the API key (different accounts can use the same key)

How to Generate Keys

Client-Side

UUID v4: "550e8400-e29b-41d4-a716-446655440000"

Use UUID v4. It's universally supported, has negligible collision probability, and requires no coordination.

Alternative: Deterministic keys — hash the operation parameters to generate the key. hash(user_id + amount + currency + timestamp). This way, retrying the same operation automatically uses the same key without the client storing it.

Common Mistakes

MistakeImpactFix
No idempotency on paymentsDuplicate chargesIdempotency key on all POST endpoints
Server-generated keysClient can't retry safelyClient generates keys
No key expirationUnbounded storage growthExpire after 24-48 hours
Processing not lockedRace condition — two workers process same keyLock on key before processing
Same key, different request allowedAmbiguous behaviorReturn error if request body differs
Key scoped globallyCross-tenant key collisionScope to API key or tenant

Beyond Idempotency: At-Least-Once vs Exactly-Once

  • At-most-once: Fire and forget. Request may not be processed. (No retries.)
  • At-least-once: Retry until success. Request may be processed multiple times. (Retries without idempotency.)
  • Exactly-once: Process exactly once. (Retries + idempotency.)

Most APIs should aim for at-least-once delivery with idempotent handlers, achieving effectively exactly-once processing.


Designing reliable APIs? Explore API best practices and tools on APIScout — architecture guides, comparisons, and developer resources.

Comments