Stripe PaymentIntent Already Confirmed Error
Root cause
Your code attempted to call confirm on a Stripe PaymentIntent that was already in confirmed or succeeded state — typically caused by a webhook handler processing the same event twice, a retry loop that does not check PaymentIntent status before confirming, or a race condition between two concurrent billing processes.
Symptoms
- • Stripe API returns an error stating the PaymentIntent has already been confirmed
- • The subscription billing attempt results in a code error rather than a payment failure
- • Duplicate webhook events are triggering confirm calls on the same PaymentIntent
- • Payment is charged successfully once, but the billing code throws an exception on the second confirm attempt
How to fix it
Add a status check before every PaymentIntent confirm call
Before calling stripe.paymentIntents.confirm(id), retrieve the PaymentIntent status: stripe.paymentIntents.retrieve(id). Only call confirm if status is requires_confirmation. If status is already succeeded or requires_action, do not call confirm — handle each status appropriately. This guard prevents already-confirmed errors regardless of how many times the code path is triggered.
Implement webhook deduplication to prevent double processing
The most common cause of already-confirmed errors is processing the same webhook event twice. Implement event deduplication: when a Stripe event arrives, store its ID in your database before processing. On the next event delivery, check the ID against stored IDs — if already present, return 200 without processing. Stripe guarantees at-least-once delivery, so your handler must be idempotent.
Eliminate concurrent billing processes for the same subscription
If two processes can simultaneously trigger billing for the same subscription (e.g., a scheduled job and a manual retry), add a distributed lock: before starting a billing operation for a subscription ID, acquire a lock (Redis, database row lock) that prevents concurrent billing for the same subscription. Release the lock after the billing attempt resolves. This eliminates race conditions that produce already-confirmed errors.
Frequently asked questions
Does a PaymentIntent already-confirmed error mean the customer was double-charged?
Usually no — the already-confirmed error is thrown before a second charge is created. The error means your code tried to confirm again, but Stripe rejected the request because confirmation already happened. Verify by checking the Stripe charge list for the customer: if you see only one charge, no double-billing occurred. If two charges exist, issue a refund immediately.
How do idempotency keys differ from PaymentIntent status checks for preventing this error?
Idempotency keys prevent duplicate API resources from being created (e.g., two charges). PaymentIntent status checks prevent invalid operations on existing resources (e.g., confirming an already-confirmed intent). You need both: idempotency keys ensure you do not create duplicate PaymentIntents, and status checks ensure you only perform valid operations on the PaymentIntents that do exist.
Detect this error automatically
Free cross-stack scan finds all billing errors in 60 seconds.
Run Free Scan →