Expand description
Idempotency keys: make POST /payments safe to retry.
A client sends a payment, the network drops before the answer arrives, and
now it does not know whether the payment happened. Retrying risks charging
twice; not retrying risks not charging at all. The way out, which Stripe
made standard, is for the client to name each attempt: an Idempotency-Key
header, chosen by the client, that the server remembers. The first request
with a key runs; every later one with the same key gets the first one’s
response back, without running anything.
r.group("/api", |api| {
api.middleware(Idempotency::new(&cache));
api.post("/payments", PaymentController::store);
});Three things the client can see:
- a replay carries
Idempotent-Replayed: true, so a client can tell a remembered answer from a fresh one; - the same key with a different request — another amount, another path —
is a
422, because silently returning the old answer to a new question is how a client ends up believing something that is not true; - a key whose first request is still running is a
409, withRetry-After: 1, rather than a second execution.
Only 2xx and 4xx answers are remembered. A 5xx means the server failed, and the client’s retry should get another go, not a copy of the failure.
Keys are scoped to the caller — the Authorization header when there is
one, otherwise the client address — so two tenants who both chose
order-1 do not collide. Change the scope with Idempotency::scope_by
when the application has a better notion of who is calling.