Skip to main content
Critical for write operations that involves Flight booking Idempotency guarantees that the same request executed multiple times produces exactly the same result β€” no side effects, no duplicate actions, no inconsistent state.

Why It Matters

Distributed systems are unreliable by nature. Network failures, timeouts, and unexpected retries are not edge cases β€” they are inevitable. Without idempotency, any of the following can silently corrupt your data:

Duplicate Bookings

A retry creates two reservations for the same user on the same slot.

Double Charges

A payment request retried after a timeout bills the customer twice.

Inconsistent State

Partial writes leave your database in an undefined, unrecoverable state.
Even a single duplicate transaction in a financial system can trigger compliance issues, failed audits, and customer disputes. Idempotency is non-negotiable.

How It Works

Every mutating request should include an Idempotency-Key header containing a unique, client-generated identifier:
POST /v1/payments HTTP/1.1
Content-Type: application/json
Idempotency-Key: e7b8c2d1-4f3a-4c6e-9b0d-2a1f5e8c3d7b

{
  "amount": 5000,
  "currency": "usd",
  "recipient": "acct_1A2B3C4D5E"
}
When the server receives a request, it follows this logic:
1

Check for existing key

The server looks up the Idempotency-Key in its store.
2

First-time request

If no match is found, the request is processed normally and the result is persisted alongside the key.
3

Duplicate request (same payload)

If the key already exists and the payload matches, the original response is returned immediately β€” no reprocessing.
4

Duplicate request (different payload)

If the key exists but the payload differs, the server returns a 422 Unprocessable Entity conflict error.

Behavior Reference


Implementation Guide

Generating keys

Always generate keys on the client side, before the request is sent. UUIDs (v4) are the standard choice.
import { v4 as uuidv4 } from "uuid";

const idempotencyKey = uuidv4();
// β†’ "e7b8c2d1-4f3a-4c6e-9b0d-2a1f5e8c3d7b"

Persisting keys

Store the key before you send the request β€” not after. If your process crashes mid-flight, you need the key available for the retry.
//  Correct order of operations
await db.pendingRequests.create({ key: idempotencyKey, payload });
const response = await api.post("/v1/payments", payload, {
  headers: { "Idempotency-Key": idempotencyKey },
});
await db.pendingRequests.update({ key: idempotencyKey, status: "complete", response });

Retry with backoff

Combine idempotency keys with exponential backoff for safe, automatic retries:
async function postWithRetry(url: string, payload: object, retries = 3) {
  const idempotencyKey = uuidv4();

  for (let attempt = 0; attempt < retries; attempt++) {
    try {
      return await fetch(url, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "Idempotency-Key": idempotencyKey, // Same key on every retry
        },
        body: JSON.stringify(payload),
      });
    } catch (err) {
      if (attempt === retries - 1) throw err;
      await sleep(2 ** attempt * 100); // 100ms, 200ms, 400ms…
    }
  }
}
Always reuse the same key across all retry attempts for a single logical operation. Generating a new key on each retry defeats the purpose entirely.

Best Practices

Use UUIDs

Generate a cryptographically random UUID v4 for every new logical operation. Never derive keys from request data.

Persist before sending

Write the key to durable storage before making the request so retries after crashes use the original key.

Scope to one operation

One key = one operation. Never reuse a key for a different action, even if the payload looks similar.

Common Mistakes

MistakeWhy It’s DangerousFix
Reusing keys across different requestsA cached response from operation A is returned for operation BGenerate a fresh UUID per operation
Not persisting keys before sendingA crash between send and store means retries use a new key, causing duplicatesStore the key first, then send
Ignoring 422 conflict errorsSilent data corruption or unintended overwritesTreat 422 as a hard stop β€” investigate before retrying
Using sequential IDs as keysPredictable keys can collide across clients or sessionsAlways use UUID v4 or a CSPRNG-generated value
Generating a new key per retryEach retry is treated as a fresh request, defeating idempotencyOne key per logical operation, reused on every retry attempt

Key Expiry & TTL

Idempotency keys are not stored forever. Most systems enforce a TTL (typically 24 hours). After expiry, resubmitting the same key is treated as a brand-new request.
Do not rely on idempotency keys as a long-term deduplication mechanism. For long-lived deduplication, implement application-level checks (e.g., unique constraints in your database).