Any endpoint that moves money must survive being called twice. Networks time out, clients retry, users double-click — and without idempotency, a retried withdrawal pays out twice. The fix is a small amount of discipline at the boundary.
The client supplies the key
The caller generates a unique key per logical operation and sends it with the request:
POST /withdrawals
Idempotency-Key: 8f14e45f-ceea-467a-9d0e-...
{ "amount": 50.00, "currency": "AUD" }
The server stores the key with the result of the first successful call. A retry with the same key returns the stored response instead of executing again.
Store the outcome, not just the key
def withdraw(key, amount, account):
existing = db.get_idempotent(key)
if existing:
return existing.response # replay, do not re-execute
with db.transaction():
result = execute_payout(account, amount)
db.put_idempotent(key, result) # same tx as the side effect
return result
The record and the side effect must commit in the same transaction. If they don't, a crash between them either double-pays (key not saved) or loses the payout (key saved, payout rolled back).
Scope and expiry
Keys are scoped per account and per endpoint, so one user's key can't collide with another's. Expire them after a day or two — long enough to cover any sane retry window, short enough that the table doesn't grow without bound.
Reference
Cashier flows are a good place to see this pattern under real load, since a duplicated withdrawal is an immediate, visible failure. Watching how a withdrawal at a site like wantedwin casino review confirms a request exactly once — even if you refresh mid-submit — is a concrete example of idempotency done right.
Takeaway
Take a client-supplied key, persist the result alongside the side effect in one transaction, and replay stored responses on retry. A few lines at the boundary turn a double-payout bug into a non-event.
Top comments (0)