It is 9 pm on a salary day. You press Pay in the Mangrove app with a mobile wallet like M-Pesa, UPI or bKash. The spinner turns, the train goes into a tunnel, and the app shows "Something went wrong". You press Pay again. Did you just pay twice?
In a well-built checkout, no. The trick is called an idempotency key, and it is one of the most useful ideas in system design.
Why the first request is a mystery
When the app gets no answer, there are three possibilities, and the app cannot tell them apart:
- The request never reached the server.
- The server got it, charged you, and the answer got lost on the way back.
- The server is still working on it.
If the app sends the payment again, case 2 charges you twice. If it never retries, case 1 leaves your order stuck. You need a way to retry safely.
How the key works
- The app creates a key once per payment, before the first request. A random id is fine. It sends the key with the request, and it sends the same key with every retry of that payment.
- The server records the key before charging. It inserts a row with the key into a table that has a unique constraint on the key. If the insert fails because the key exists, this is a retry.
- On a retry, the server returns the saved result. If the first attempt finished, the retry gets the same answer: "paid, receipt 1042". If the first attempt is still running, the retry gets "still processing, check again in a few seconds".
The database does the hard part. Two requests with the same key can arrive at the same moment, and the unique constraint lets only one of them win. You can see this in action in APIs done right and Transactions, ACID, isolation and locks.
The parts people forget
- Retries need a pause. If thousands of phones retry at the same instant after a short outage, they cause a second outage. Wait a little longer after each failure and add a random delay. Failure is normal shows why.
- The wallet provider needs the key too. The payment provider usually supports its own idempotency key. Pass yours through, so a retry from your server does not charge twice either.
- Keys expire. Keep them for a day or so, long enough for any retry, then delete them.
Next time someone says "just retry it", ask: what makes the retry safe?