Why automatically retrying a failed HTTP POST request might accidentally charge a user's credit card twice.
When you send an HTTP POST to charge a credit card, the request travels across the internet, the server charges the card, and sends back a "Success!" response. But what if the user's WiFi drops while the "Success" response is traveling back? The client sees a network timeout. The client thinks the request failed, so it automatically Retries. The server receives a second POST request, and charges the card a second time. To fix this, critical APIs must be Idempotent—meaning no matter how many times you send the exact same request, the result only happens once.
To make an endpoint idempotent, the client must generate a unique, random string (a UUID) called an Idempotency Key, and attach it to the HTTP Header. The server looks at this key before doing any work. If the server has seen this key before, it completely skips the database logic and simply replies with the cached result of the original successful request.
// 1. Client generates a unique ID for this specific action
const idemKey = "pay_9876xyz";
// 2. Client sends the POST with the header
await fetch("/charge", {
method: "POST",
headers: { "Idempotency-Key": idemKey },
body: JSON.stringify({ amount: 50 })
});
// 3. (Server-side Logic)
const previousResult = await redis.get(req.headers["Idempotency-Key"]);
if (previousResult) {
// We already did this! Return the cached success, do NOT charge again.
return res.send(previousResult);
}
// Otherwise, proceed to charge the card...
Building an idempotent API adds complexity. You have to store every single Idempotency Key you process in a database (like Redis) along with the HTTP response, usually for 24-48 hours. This requires extra storage and an extra database read before every single POST request, adding slight latency to the hot path.
SETNX in Redis) on the Idempotency Key before processing!