AltorLabFree Scan →
HomeError Codesunexpected_state
stripeshopify

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

How to fix it

1

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.

2

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.

3

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 →