Stripe’s payment intent lifecycle: where charges wait and why

PENGUIN LIFECYCLE H.JPG

The newsletter for newsletter operators

Daily field notes on deliverability, AI tools, hosting, and monetisation. No "top 10 plugins" filler — real tools, real numbers, real failures.

Stripe's payment intent lifecycle: where charges wait and why
Photo: Zina Deretsky, National Science Foundation via Wikimedia Commons (Public domain)

Most solo operators think Stripe charges happen instantly. Click the button, money moves, done. But behind every payment sits a state machine with six distinct phases—and knowing where your charge actually is can save you hours of customer-support confusion and lost revenue.

Payment intents are Stripe’s core transaction object. They track a single charge from creation to completion, and they don’t always move in straight lines. Here’s how the lifecycle actually works, what delays the flow, and one non-obvious trick that prevents duplicate charges.

The six states every payment intent moves through

When a customer submits payment, Stripe creates a PaymentIntent object. It starts in one of these states:

  • requires_payment_method – Intent created, but no card attached yet. Happens when you create the intent server-side before the customer sees the form.
  • requires_confirmation – Card attached, waiting for you to confirm the charge. Manual confirmation mode only.
  • requires_action – 3D Secure or bank authentication needed. This is where most “the payment didn’t go through” support emails come from.
  • processing – Stripe is talking to the bank. Usually lasts 1–3 seconds, but can hang for minutes on certain card networks.
  • requires_capture – Charge authorized, waiting for you to capture it. Only applies if you’ve enabled manual capture.
  • succeeded – Money secured. You can fulfill the order.

Failed charges land in canceled or move back to requires_payment_method if you want to let the customer retry with a different card.

Where charges stall—and what to check

The most common hangup is requires_action. Stripe redirects the customer to their bank for 3D Secure authentication, but if the popup gets blocked, the redirect fails silently, or the customer closes the window, the intent never progresses. The charge stays pending indefinitely.

Stripe’s webhook fires a payment_intent.requires_action event. If you’re not listening for it, you’ll never know the customer is stuck. Set up a webhook listener that emails the customer a direct authentication link when this fires—Stripe includes it in the next_action.redirect_to_url field.

Processing delays happen more often than Stripe’s docs suggest. Visa and Mastercard usually respond in under two seconds, but Discover, certain co-branded cards, and international issuers can take 15–30 seconds. If your checkout flow redirects to a confirmation page immediately after submission, customers see a blank order and panic. Wait for the payment_intent.succeeded webhook before showing success.

Manual capture mode (requires_capture) is useful for high-value orders where you want to authorize the charge but delay settlement until you ship. But Stripe auto-cancels uncaptured intents after seven days. If you’re running a pre-order or made-to-order business, you need to either capture within a week or switch to a different payment flow entirely—like invoices with NET terms.

The idempotency key trick

Here’s the non-obvious tip: Stripe’s API accepts an idempotency_key parameter on every PaymentIntent creation request. If your customer double-clicks the submit button, or your server retries a failed request, Stripe checks the key. If it matches an existing intent created in the last 24 hours, Stripe returns the original object instead of creating a duplicate charge.

Most client libraries don’t set this automatically. You need to generate a unique key per transaction—usually a hash of the user ID, cart total, and timestamp—and pass it explicitly:

stripe.paymentIntents.create({ amount: 5000, currency: 'usd' }, { idempotencyKey: 'user123_1697654400_5000' });

Without this, double-click bugs charge customers twice. Stripe won’t refund both automatically—you’ll eat the dispute fee and the support time.

When to use automatic vs. manual confirmation

Stripe defaults to automatic confirmation: as soon as the card is attached, the charge fires. This works for most solo operators. But if you’re running a multi-step checkout—cart → shipping → payment—and you want to confirm inventory or apply fraud checks before charging, switch to manual confirmation mode.

Set confirmation_method: 'manual' when creating the intent. The intent will pause at requires_confirmation until you call stripe.paymentIntents.confirm(). This gives you a hook to run your own validation logic server-side before money moves.

Manual confirmation adds complexity—you need to handle the confirmation call in your webhook flow or backend—but it prevents scenarios where Stripe charges a customer for an out-of-stock item because your inventory check ran after payment.

What to monitor in your dashboard

Stripe’s dashboard shows payment intent states in real time, but it doesn’t alert you when intents get stuck. Set up a daily filter for intents in requires_action or processing that are older than 30 minutes. Those are your lost conversions—customers who started checkout, hit a snag, and never came back.

For automated monitoring, pull the payment_intent.processing and payment_intent.requires_action webhook events into your analytics tool and track time-to-resolution. If your median processing time jumps above five seconds, you’re either seeing more international cards or your webhook handler is slow.

One more thing: Stripe’s test mode doesn’t simulate processing delays. Test cards move instantly from requires_confirmation to succeeded. If you want to test your timeout logic, you need to use a live-mode charge with a real card in a staging environment, then immediately refund it.

Got a payment-flow question? Reply to this email—I’ll cover it in a future breakdown. And if you found this useful, forward it to another operator who’s wiring up Stripe for the first time.

The newsletter for newsletter operators

Daily field notes on deliverability, AI tools, hosting, and monetisation. No "top 10 plugins" filler — real tools, real numbers, real failures.

Other newsletters you might like

Local Edinburgh

Local Edinburgh is a website that is dedicated to the promotion of Edinburgh as a travel destination. Edinburgh is Scotland’s capital city renowned for its heritage culture and festivals.

Subscribe

Ireland Moments

A daily newsletter and blog celebrating Ireland — its places, culture, music, history and the stories worth sharing from the Emerald Isle.

Subscribe

Love London

A newsletter for Londoners who want to rediscover their own city. Travellers planning their first or fifth visit. Anglophiles who fell in love with London through literature, film, or a rainy afternoon on the South Bank.

Subscribe

Scottish Rugby Fans

The best Scottish rugby updates, straight to your inbox — Six Nations, the Nations Championship, Glasgow and Edinburgh. Only when there's something worth reading.

Subscribe

Newsletters via the One Two Three Send network.  ·  Want your newsletter featured here? Click here