How to design idempotent API operations
What idempotency means for create requests and outbound calls, how idempotency keys work, how to choose a key, and how to build receivers that tolerate at-least-once delivery.
What idempotent means
An operation is idempotent when performing it twice has the same effect as performing it once. Retries become safe: a client that lost the response can send the request again without creating a duplicate order, payment, or email.
Where duplicates come from
- A client times out after the server committed the write and retries.
- A durable job or webhook delivery is attempted twice because the first response was lost.
- A user double-clicks, or a deploy replays a queue.
Idempotency keys on create requests
The client chooses a key that identifies the intent, such as order-123-completed, and sends it as Idempotency-Key. The server stores the key with the result. A repeat with the same key and body returns the stored result; a repeat with a different body is a conflict.
Idempotency-Key: order-123-completed| Situation | Flowyn response |
|---|---|
| First request | Creates the resource |
| Same key, same body (within 24 h) | Returns the original response, no duplicate |
| Same key, different body | 409 idempotency_mismatch |
| Different key | Creates a new resource |
Choosing a good key
- Derive it from the business event:
invoice-881-sync,user-42-welcome-email. - Do not generate a random UUID per attempt; that defeats the purpose.
- Keep it stable across retries and across processes.
Idempotency on outbound calls
Flowyn sends a stable id on every outbound attempt: Idempotency-Key: <job id> on HTTP jobs and webhook-id: <event id> on webhook deliveries. Receivers should store processed ids and return 2xx for repeats without redoing the work.
// receiver
const id = req.headers["webhook-id"];
if (await seen(id)) return res.status(200).end();
await process(req.body);
await markSeen(id);
res.status(200).end();Designing the operation itself
- Prefer upserts keyed by a natural id over blind inserts.
- Make state transitions explicit:
pending → paidis safe to apply twice;balance += 10is not. - Return the current resource on repeat, not an error, so clients can resume.
Related
Next step
Create a job with an idempotency key in the Playground.
