Back to all terms
Payment
Paymentsintermediate

Idempotent Payment Requests

Using idempotency keys on payment API calls to ensure that retried requests due to network failures or timeouts produce the same result without creating duplicate charges.

Also known as: idempotency keys, payment idempotency, safe payment retries, at-most-once payments

Description

Idempotency in payment processing ensures that performing the same operation multiple times produces the same outcome as performing it once. This is critical because network failures, client timeouts, and server errors can leave the caller uncertain whether a request succeeded. Without idempotency, retrying a failed payment creation could result in the customer being charged twice. Stripe supports idempotency by accepting an Idempotency-Key header on all POST requests; if Stripe receives a request with a key it has seen before (within 24 hours), it returns the cached response from the original request.

The idempotency key should be generated deterministically based on the operation's intent, not randomly. For example, use a combination of user ID, order ID, and amount (e.g., `pay_user123_order456_2999`) so that the same logical payment always maps to the same key. Random UUIDs as idempotency keys protect against accidental double-clicks but not against application-level retry bugs where a new UUID is generated per attempt. Store the idempotency key alongside your order record before making the Stripe call, so you can always retry with the same key.

Beyond Stripe's built-in idempotency, your own system should enforce at-most-once semantics at the application layer. Use database constraints (unique indexes on order_id + payment_intent_id) and state machine patterns (order status must be 'pending' to initiate payment) to prevent duplicate payment attempts regardless of the payment provider's idempotency support. This defense-in-depth approach protects against double charges even in distributed systems with multiple server instances.

Prompt Snippet

Generate idempotency keys deterministically from the business operation context (e.g., `payment_${orderId}_${amount}_${currency}`) rather than random UUIDs, and pass them via the idempotencyKey option in stripe.paymentIntents.create(). Persist the idempotency key in your orders table before calling Stripe so retries reuse the same key. Implement application-level idempotency by adding a UNIQUE constraint on (order_id, status) and using a compare-and-swap pattern: UPDATE orders SET status = 'processing' WHERE id = ? AND status = 'pending' returning the affected row count to gate the Stripe call.

Tags

idempotencyreliabilitypaymentsretrydouble-chargestripe