keepcalculator

06 Numbers and Powers

Idempotency: How It Stops Retry Double Charges

Card No. 06·48845 · Updated · Standard formula, shown below

Idempotency is a clever programming concept that ensures an operation produces the same result no matter how many times you repeat it. In payment systems, this means a retry—whether due to a network timeout, a user double-click, or a server crash—will never charge a customer twice. This guide explains how idempotency works, how to implement it, and what to do when things go wrong.

What You’ll Need

  • An API endpoint that handles payment or order creation (e.g., POST /checkout or POST /orders).
  • A database or key-value store (like Redis) to persist idempotency keys.
  • Basic understanding of HTTP methods and status codes.

Step-by-Step

  1. Generate a unique idempotency key for each logical request. This is typically a UUID (universally unique identifier) generated on the client side. The key must be unique for each distinct operation, but identical for retries of the same operation. For example, when a user clicks “Place Order,” generate a key like 6b8f2c3a-9e1d-4f5a-b7c8-9d0e1f2a3b4c and send it in the request header (e.g., Idempotency-Key) or in the request body.
  2. On the server, check if the idempotency key already exists. When your API receives a request, first look up the key in your database or cache. If it exists, retrieve the stored response and return it to the client without processing the request again. This is the core of idempotency—it short-circuits duplicate submissions.
  3. If the key does not exist, process the request normally. Perform the actual work: charge the payment, create the order, update inventory, etc. This is the first time this key is seen, so it’s safe to execute.
  4. Store the idempotency key along with the response. After successfully processing the request, save the key and the corresponding HTTP response (status code, body, headers) in your data store. Set an expiration time (e.g., 24 hours) to prevent the store from growing indefinitely. The stored response must be immutable—once saved, it cannot change.
  5. Handle concurrent requests with the same key. If two identical requests arrive simultaneously (e.g., due to a double-click), you need to prevent both from processing. Use a database unique constraint on the idempotency key column, or use a lock (like Redis SETNX) to ensure only one request proceeds. The other request should wait for the first to complete and then return the stored response.
  6. Return the stored response for all subsequent retries. When a retry comes in with the same key, your server returns the exact same response as the original request. This ensures the client sees a consistent result, even if the original response was lost in transit.

Common Mistakes

  • Generating a new key for each retry. If the client creates a fresh UUID on every attempt, the server will treat each as a new request, leading to double charges. The key must be tied to the logical operation, not the HTTP request.
  • Ignoring the key on the server. Some developers forget to check the key or only check it partially. The check must happen before any side effects (like charging a card) occur.
  • Storing the key after the response is sent. If you save the key after sending the response, a retry that arrives before the save completes will slip through. Save the key and response atomically, ideally in the same database transaction as the payment.
  • Not handling concurrent requests. Without a unique constraint or lock, two simultaneous requests with the same key can both pass the initial check and process the payment twice.
  • Using a key that is not unique enough. Using a simple counter or timestamp can lead to collisions. Always use a cryptographically random UUID.

Tips

  • Use a standardized header name. Many APIs use Idempotency-Key (Stripe) or X-Idempotency-Key (PayPal). Pick one and document it clearly.
  • Set a reasonable expiration. 24 hours is common, but consider your use case. If a user might retry a payment after a day, extend it.
  • Include the idempotency key in logs. This makes debugging much easier when you need to trace a duplicate request.
  • Return the same status code. If the original request returned 200 OK, the retry should also return 200 OK, not 201 Created or 409 Conflict.
  • Test with simulated network failures. Use tools like Charles Proxy or network throttling to simulate timeouts and verify your retry logic works.

Troubleshooting

  • Retry still charges twice. Check if the client is sending the same key. Look at the server logs to see if the key was present in both requests. If not, fix the client code to reuse the key.
  • Concurrent requests cause double charge. Ensure your database has a unique index on the idempotency key column. If you’re using Redis, use the SETNX command to acquire a lock before processing.
  • Stored response is lost. If your data store is cleared or the key expires, a retry will be treated as new. Increase the expiration time or use a more durable store.
  • Key exists but response is incomplete. This can happen if the server crashed after storing the key but before saving the response. In that case, you may need to manually reconcile the transaction. Some systems store a “processing” state and return a 409 or 425 status to indicate the request is already in progress.
  • Client receives a different response on retry. Ensure the stored response is serialized exactly. If you store only the status code but not the body, the retry may appear different.

Frequently Asked Questions

What exactly is an idempotency key?

It’s a unique identifier that represents a single logical operation. When the same key is sent again, the server knows it’s a retry and returns the same result instead of processing again.

Do I need idempotency for GET requests?

No. GET requests are naturally idempotent because they don’t change state. Idempotency is critical for POST, PUT, or PATCH requests that create or modify resources.

How long should an idempotency key be valid?

It depends on your retry window. Typically 24 hours is sufficient, but if your clients might retry after a longer period, extend it. The key should be long enough to cover the maximum retry time.

Can I use the same idempotency key for different operations?

No. Each logical operation must have its own unique key. Reusing a key for a different operation would cause the server to return the old response, which is incorrect.

What if the server crashes after charging but before storing the key?

This is a classic distributed systems problem. To handle it, you need to make the payment and the key storage atomic—for example, by using a database transaction that includes both the payment record and the idempotency key. If that’s not possible, you may need a reconciliation process to detect and refund duplicate charges.

Is idempotency the same as a unique constraint?

Not exactly. A unique constraint prevents duplicate entries in a database, but idempotency is a broader concept that includes returning the same response. A unique constraint is one way to implement idempotency, but you also need to store and return the response.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →