Ensuring Idempotency
1. Understanding Idempotent Operations
| Method | Idempotent | Why |
|---|---|---|
| GET, HEAD, OPTIONS | Yes | No state change |
| PUT | Yes | Same body → same final state |
| DELETE | Yes | State after = absent |
| POST | No (default) | Creates new each time |
| PATCH | Depends | Merge yes; relative ops no |
2. Implementing Idempotent POST Requests
| Technique | How |
|---|---|
| Idempotency-Key header | Server stores key+response; replay returns cached |
| Client-supplied UUID in body | Reject duplicates by ID constraint |
| Natural unique key | Use email, SKU as deduplicator |
3. Using Idempotency-Key Header
Stripe-popularized convention; now a draft IETF RFC.
Example: Idempotency-Key
POST /payments HTTP/1.1
Idempotency-Key: 7f2e1a4b-9c3d-4e5f-8a1b-2c3d4e5f6a7b
Content-Type: application/json
{"amount": 100, "currency": "USD"}
# First call: 201 Created (charge processed)
# Retry same key: 201 Created (cached response, NO duplicate charge)
4. Storing Idempotency Keys
| Storage | Stored Fields |
|---|---|
| Key | Idempotency key (string) |
| Request fingerprint | Hash of method+path+body |
| Response | Status, headers, body |
| Created/expires | Timestamps |
| Lock state | To handle concurrent retries |
5. Handling Duplicate Requests
| Scenario | Server Response |
|---|---|
| Same key, same payload | Return cached response |
| Same key, different payload | 422 (key reused for different request) |
| Same key, in-flight | Wait or 409 Conflict |
6. Returning Cached Responses
| Header to Echo | Purpose |
|---|---|
| Original status code | Same as first call |
Idempotent-Replayed: true | Indicates cached reply |
| Original body bytes | Byte-for-byte identical preferred |
7. Implementing Idempotency for Payments
| Practice | Reason |
|---|---|
| Mandatory Idempotency-Key | Prevent double charges on retry |
| UUID v4 / ULID | Unique, unguessable |
| Per-user key namespace | Avoid cross-user collisions |
| Persist 24h+ | Cover long retry windows |
8. Handling Idempotency Key Expiration
| TTL | Use Case |
|---|---|
| 24 hours | Stripe default |
| 7 days | Long-running batch jobs |
| After expiration | Same key allowed for new request |
9. Validating Idempotency Key Format
| Rule | Reason |
|---|---|
| Min 16 chars, max 255 | Prevent short collisions / abuse |
| UUID/ULID recommended | Sufficient entropy |
| Reject empty/whitespace | Avoid accidental disabling |
10. Documenting Idempotency Behavior
| Doc Field | Content |
|---|---|
| Endpoints supporting key | List explicitly |
| Key format | UUID, length |
| TTL | Storage duration |
| Conflict behavior | 422 on payload mismatch |