unexpected_state: Stripe PaymentIntent Fix
Root cause
A PaymentIntent in Stripe has a strict state machine. unexpected_state fires when code attempts an operation that is not valid for the PaymentIntent's current status — for example, confirming an already-succeeded PaymentIntent, or capturing a cancelled PaymentIntent. This typically indicates a race condition or duplicate operation in your integration.
Symptoms
- • Stripe API returns error code unexpected_state on a PaymentIntent operation
- • The subscription renewal fails despite the PaymentIntent existing in Stripe
- • Duplicate charge attempts may be occurring due to retry logic not checking PaymentIntent status first
- • Shopify subscription billing attempt fails without a corresponding gateway error
- • Webhook handling is triggering duplicate operations on the same PaymentIntent
How to fix it
Retrieve the PaymentIntent and check its current status
Before any operation on a PaymentIntent, retrieve its current status via the Stripe API: GET /v1/payment_intents/{id}. Valid statuses are: requires_payment_method, requires_confirmation, requires_action, processing, requires_capture, canceled, succeeded. Map your intended operation against the current status to confirm it is valid before proceeding.
Add idempotency keys to all PaymentIntent operations
Implement idempotency keys on every Stripe API call that creates or modifies a PaymentIntent. Use a deterministic key based on the subscription ID and billing cycle date: e.g., sub_{id}_cycle_{date}. Idempotency keys prevent duplicate operations and make retries safe — Stripe returns the same result for repeated calls with the same key.
Audit webhook handlers for duplicate processing
unexpected_state is often caused by webhook handlers that process the same event multiple times. Implement webhook event deduplication by storing processed event IDs in your database and skipping events you have already handled. Stripe delivers each webhook at least once — your handler must be idempotent.
Frequently asked questions
Does unexpected_state mean the customer was charged twice?
Not necessarily. unexpected_state typically means a second operation was attempted on an already-processed PaymentIntent, not that a second charge occurred. Retrieve the PaymentIntent to check its status and verify whether a charge was created. A succeeded PaymentIntent with unexpected_state on a second confirm call usually means the first confirm succeeded.
How do I prevent unexpected_state errors in subscription integrations?
Always retrieve PaymentIntent status before operating on it. Use idempotency keys on all writes. Implement webhook deduplication. Never fire parallel threads that could both operate on the same PaymentIntent simultaneously. Design your billing retry logic to check PaymentIntent status first rather than blindly creating new operations.
Detect this error automatically
Free cross-stack scan finds all billing errors in 60 seconds.
Run Free Scan →