24. APIs and Idempotency
24.7 What is the double-charge problem?
Idempotency
At the machine: You tap your card. The screen freezes for ten seconds. Did it go through? You can't tell. If you tap again and it had gone through, you've paid twice. If you don't and it hadn't, you get no water.
In the system: The bookstore's checkout sends POST /charges for ₹1,248. The payment provider charges the card and sends back 201 Created, but the response is lost: a timeout, a dropped connection, the checkout server restarting. From the client's side, "it failed" and "it worked but the reply got lost" look exactly the same.
Figure: the client can't tell a failure from a lost reply; only the server can.
If the client retries, the customer may be charged twice. If it doesn't, the order may be left unpaid. In the chapter's code, a client that retries once after a lost reply creates two charges for order 881.
This isn't rare at scale. Timeouts, mobile networks, load balancer restarts and deploys all cause lost replies. And retries come from everywhere: your code, HTTP libraries, proxies, users pressing "Pay" again.
The fix is to make the operation idempotent: doing it twice must have the same effect as doing it once. GET, PUT and DELETE are idempotent by design; creating things with POST needs help.
Memory code: "A lost reply looks like a failure; retrying blindly pays twice."