Idempotency

What idempotency means precisely, idempotency keys, and the financial-systems pattern.

Overview

Why idempotency matters

An idempotent operation can be performed multiple times with the same result: f(x) = f(f(x)). It is critical for retries, because retrying a non-idempotent operation causes duplicate side effects.

The canonical case is a payment API. A network timeout triggers a client retry; the first request succeeded but its response was lost, so the retry creates a second charge. With an idempotency key, the server detects the duplicate and returns the original response.

> [!IMPORTANT] The industry standard is Stripe-style keys: a UUID per logical operation, sent as Idempotency-Key, stored with the response for 24 hours.

Which operations need keys

PUT and DELETE are naturally idempotent, and GET is by definition. The danger zone is POST for creation: charges, messages, transactions.

  • PUT /users/123 with {name: "Alice"} — Yes — Same state
  • DELETE /users/123 — Yes — No-op after the first
  • GET /users/123 — Yes — Same read
  • POST /payments — No — Multiple charges
  • POST /tweets — No — Duplicate tweets
  • PATCH /users/123/credits +10 — No — Wrong total

The retry problem

When a non-idempotent request fails, you cannot tell which case you are in.

The Stripe-style pattern

  • 1 — Client decides on a non-idempotent operation and generates UUID abc-123
  • 2 — Client sends POST /payments with header Idempotency-Key: abc-123
  • 3 — Server processes and stores (key="abc-123", result=, ttl=24h)
  • 4 — Server responds
  • 5 — On network failure or timeout, the client retries with the same key
  • 6 — Server finds the key and returns the original response without reprocessing

Implementation details

  • Dedup key — The idempotency key, a UUID
  • Dedup value — Response body plus status code
  • TTL — Matches the retry window, typically 24 hours
  • Storage — Redis, fast but memory-capped, or a DB, durable
  • Concurrency — Two simultaneous requests with one key: the second waits, via a per-key pessimistic lock or "INSERT IF NOT EXISTS"; it then returns the same result or "in progress; come back later"
  • Same key, different body — 409 Conflict; the key names one operation and is being misused
  • Retry before the first completes — 409 "already in progress; same key"; client polls or waits
  • TTL expired — Treated as a fresh request; clients must respect the TTL

Where idempotency lives

  • Application — Most common: check Redis or a DB at the start of every non-idempotent handler
  • API gateway — Less common; must know which endpoints are non-idempotent
  • Client library — The Stripe SDK handles it client-side automatically

Some APIs make idempotency mandatory: AWS's state-machine API, Stripe's payment APIs, and most cloud services' write operations. Others require a client-supplied idempotency token on every write, so non-idempotent operations are impossible by design.

Financial systems pattern

Stripe's docs: "Stripe's API has built-in support for idempotency for safely retrying API requests without accidentally performing the same operation twice."

The exception: distributed sagas

Long-running, multi-step operations need different handling. Each saga step is idempotent and retried from where it failed, while compensation actions handle rollbacks.

> [!TIP] Sagas are more complex than single-call idempotency, but they are what cross-service transactions need.

Key concepts

Idempotent
Multiple calls = same result. f(x) = f(f(x)). Required for safe retries.
Idempotency key
Client-generated unique ID per operation. Server deduplicates by key. Standard for non-idempotent APIs.
Dedup table
Storage of (key, result) for retried operations. Redis (fast) or DB (durable). TTL matches retry window.
Concurrency on same key
Second simultaneous request with same key: lock or 409 Conflict. Don't process twice.
Stripe / AWS API pattern
Idempotency built into the API. Client provides key; server handles dedup. The standard for write APIs.
Saga pattern
Multi-step distributed workflow; each step idempotent; compensations for rollback. For cross-service transactions.

When to use it

  • Designing every non-idempotent API — idempotency keys are mandatory
  • Reviewing existing retry logic — are retries safe?
  • Postmortem with duplicate side effects (double charges, duplicate posts) — was idempotency missing?
  • Cross-service workflows — saga pattern with idempotent steps

Interview questions

  • Walk me through implementing idempotent payment processing.
  • How do you handle idempotency for long-running operations?
  • When can you skip idempotency keys?

Sign up free to read the full topic

More SRE topics

Start free on Cariara · Coding problems · System design studio