Skip to main content

Module idempotency

Module idempotency 

Source
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, with Retry-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.

Structs§

Idempotency