Designing payment API retries with idempotency

If a payment request times out, it may already have run. Keep the original operation identity so you can check its result or retry under the provider’s documented rules. A new request with a new key may create a second payment attempt.

On this page

What the idempotency key protects

In HTTP, idempotency means that repeating a request has the same intended effect on the server as making it once. Responses and diagnostic logs may still differ. A payment-creation POST is not automatically idempotent.

Some payment APIs use an idempotency key to recognize repeated submissions of one operation. Check the endpoint’s rules for supported requests, parameter matching, concurrent requests, key retention and errors. An arbitrary header cannot supply those guarantees, and a key alone is not an exactly-once guarantee.

Keep the original operation when the response is lost

Order 421: the response disappears

Suppose a customer is paying $48.00 for order 421. Before the first request, the application stores the payment-operation identifier and chosen idempotency key.

The connection drops while the application waits for a response. The order still points to the original operation. Nothing about the timeout establishes whether the customer was charged.

If the provider permits a retry, use that same operation, key and unchanged parameters. A separate purchase needs its own operation and key. Two jobs that independently generate fresh keys for order 421 can still create duplicate attempts, so keep one authoritative payment-state record for the order.

Keep secrets and personal information out of keys. Save enough context to locate the provider’s payment object later through its supported reference or metadata mechanism. Do not show a successful-payment message merely because your application created an operation record.

Test the cases a simple retry cannot resolve

If two workers submit together, check how the provider handles concurrent requests using the same key. Keep one operation record and handle its documented conflict response.

If the amount changes from $48 to $52, resolve the earlier payment’s outcome before creating a replacement operation. If the old key has expired, investigate the existing payment instead of assuming that reusing the key still protects it.

For Stripe API v1, a same-key retry can return the stored response, including an HTTP 500 error. Stripe’s error guidance treats a server-error outcome as indeterminate: the request may have had side effects. Repeating the error does not establish that no money moved. Follow the documented recovery process; other APIs and versions can behave differently.

Limit retries and recover unresolved orders

  1. Use the provider’s supported SDK or retry guidance, with bounded attempts and the appropriate delay. Jitter can spread retries instead of sending them in synchronized bursts; it cannot tell you whether a payment succeeded.
  2. Retrieve the authoritative payment object and process verified provider notifications where supported. Reconcile unresolved orders rather than treating the retry schedule as a payment result.
  3. Show an accurate pending or checking state when the outcome is unknown, with a way to recover the result. Test that the customer is not simply told to pay again.
  4. Before release, assign an owner for unresolved orders. Record how duplicate notifications are handled and how the team distinguishes a separate purchase from another attempt at the same one.

References

Back to top