Network failures, timeouts, and retries are a reality of working with any API. Idempotency keys ensure that retrying a request never creates duplicate resources or performs an action twice.
When you include an Idempotency-Key header on a POST or PATCH request, the API remembers the response for that key. If you send the same request again with the same key and body, the API returns the cached response instead of processing the request again.
Copy
Ask AI
POST /api/v1/payment-linksIdempotency-Key: my-unique-key-12345Content-Type: application/json{ "name": "Premium Plan", ... }
1
First request
The API processes the request normally, creates the resource, and caches the response. The response is associated with the idempotency key and the SHA-256 hash of the request body.
2
Retry with same key + same body
The API detects the duplicate key, verifies the body hash matches, and returns the cached response immediately with an Idempotent-Replayed: true header. No new resource is created.
3
Retry with same key + different body
The API detects the duplicate key but the body hash does not match. It returns a 409 Conflict error with code idempotency_conflict. This prevents accidental misuse of keys.
Idempotency keys are cached for 24 hours from the first request. After 24 hours, the key expires and can be reused.
Only successful responses (HTTP 2xx) are cached. If the original request failed with a 4xx or 5xx error, the key is not consumed and you can retry with the same key.
The idempotency key can be any string up to 256 characters. Here are some recommended approaches:
UUID (recommended)
Deterministic key
Request hash
Copy
Ask AI
import { randomUUID } from "crypto";const key = randomUUID();// "550e8400-e29b-41d4-a716-446655440000"
UUIDs are globally unique and the simplest option for most use cases.
Copy
Ask AI
// Derive the key from the operation contextconst key = `create-link-${userId}-${productId}-${Date.now()}`;// "create-link-usr_123-prod_456-1709078400000"
Deterministic keys are useful when you want the same logical operation to always use the same key — for example, ensuring a user can only create one payment link for a specific product.
Copy
Ask AI
import { createHash } from "crypto";const key = createHash("sha256") .update(JSON.stringify({ endpoint: "/payment-links", body: data })) .digest("hex") .slice(0, 64);
Hashing the request ensures identical requests always produce the same key. Useful for queue-based systems where the same message might be processed multiple times.
Idempotency keys are scoped to your organization. Two different organizations can use the same key string without conflict. Within your organization, each key can only be used once per 24-hour window.
Always use idempotency keys for payment-related operations
Any request that creates a payment link, checkout session, or triggers a financial action should include an idempotency key. This is the most important place to prevent duplicates.
Generate the key before the first attempt
Create the idempotency key once and reuse it across all retry attempts for the same logical operation:
Copy
Ask AI
// Correct: key generated once, reused on retriesconst key = randomUUID();for (let attempt = 0; attempt < 3; attempt++) { const response = await fetch(url, { headers: { "Idempotency-Key": key }, body: JSON.stringify(data), }); if (response.ok) break;}// Wrong: new key on each retry (defeats the purpose)for (let attempt = 0; attempt < 3; attempt++) { const response = await fetch(url, { headers: { "Idempotency-Key": randomUUID() }, // Different key each time! body: JSON.stringify(data), }); if (response.ok) break;}
Do not reuse keys across different operations
Each logical operation should have its own idempotency key. Reusing a key from a previous (different) operation will result in either a cached response from the old operation or a 409 conflict.
Handle 409 conflicts gracefully
If you receive an idempotency_conflict error, it means the key was already used with a different request body. Generate a new key and retry:
Copy
Ask AI
if (error.code === "idempotency_conflict") { // Generate a fresh key and retry return createPaymentLink(data, { idempotencyKey: randomUUID() });}
Check the Idempotent-Replayed header
When the Idempotent-Replayed: true header is present, the response is a cached replay. This can be useful for logging and debugging to distinguish between fresh and replayed responses.
Use deterministic keys for exactly-once semantics
If your system processes events from a queue (e.g., Kafka, SQS, BullMQ), derive the idempotency key from the event ID or message ID. This ensures that processing the same event twice never creates duplicate resources: