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.

http
Idempotency-Key: order-123-completed
SituationFlowyn response
First requestCreates the resource
Same key, same body (within 24 h)Returns the original response, no duplicate
Same key, different body409 idempotency_mismatch
Different keyCreates 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.

js
// 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 → paid is safe to apply twice; balance += 10 is not.
  • Return the current resource on repeat, not an error, so clients can resume.

Next step

Create a job with an idempotency key in the Playground.