Short answer
Generate a new key for each distinct operation, for example a random UUID created when the person presses Pay, and keep it only for retries of that call. If you derive the key from an order or cart ID, it must change whenever the amount or items change. Stop double submits, and retry with the same key and the same parameters only after the first request has finished.
Why Stripe rejects the key, and the fix
01
The key was reused with different parameters
“The idempotency layer compares incoming parameters to those of the original request and errors if they’re not the same to prevent accidental misuse.” Stripe, Idempotent requests
Keys built from a cart, order or user ID repeat when the amount or items change, so the second request no longer matches the first. Create one key per operation, for example per Pay click, and reuse it only for retries of that identical request. If the amount or items change, that is a new operation and needs a new key.
import { randomUUID } from 'node:crypto'; const key = randomUUID(); // once per Pay click, reused only for retries const pi = await stripe.paymentIntents.create( { amount, currency: 'usd', customer }, { idempotencyKey: key }, );02
One key used for two different calls
Using the same key for creating a customer and then a PaymentIntent can fail too. Give each call its own key, for example by adding the operation name to it.
03
Two requests with the key ran at the same time
“The idempotency key provided is currently being used in another request. This occurs if your integration is making duplicate requests simultaneously.” Stripe, Error codes (idempotency_key_in_use)
A double click, a form that submits twice, or your own retry firing while the first call is still running sends the same key twice at once. Disable the button while the request runs and retry only after the first one returns.
04
Retrying with the old key after changing the request
“Retrying a cached `400` response with the same key returns the same error.” Stripe, Low-level error handling
Once a call starts running, Stripe saves its result under the key, errors included. Retrying a declined payment with a new card or amount but the old key returns the old result or the parameters error. A changed request needs a new key.
05
Retrying the right way
“Retry with the same idempotency key and parameters within the first 24 hours after the original request.” Stripe, Low-level error handling
After a network error you do not know whether Stripe ran the request. Retry with the same key and the same parameters. If you write your own retry loop, generate the key once before the loop and send it on every attempt. Stripe SDKs can also generate keys and retry automatically; the defaults depend on the SDK and version.
Checked against the vendor’s docs on : Stripe API: Idempotent requests; Stripe docs: Low-level error handling; Stripe docs: Error codes.
Rather hand it over?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
- $950Stripe Billing Fix and SetupSubscriptions, webhooks, customer portal and failed payments set up or fixed, tested in Stripe test mode.See scope and start
- from $450App Fix TicketOne reproducible bug in an existing mobile or web app, diagnosed and fixed.See scope and start
- from $450AI-Built App RescueA Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.See scope and start
Related: Stripe PaymentIntent already succeeded · Stripe webhook returns 200 but the database is not updated · Stripe “Received unknown parameter” · Stripe Invalid API Key provided