Skip to content

How Do I Recover a Customer Whose Card Was Declined Instead of Losing Them?

6 min read

A card got declined during checkout or a renewal, and you assumed that customer is gone. Usually they are not. Most declines are temporary, and Stripe can automatically retry the card and email the customer to fix it, but only if you turn those features on. Out of the box, a vibe-coded app often does neither, so a recoverable payment silently becomes a lost customer.

Why this happens

Declines are normal. A card gets flagged for a large purchase, hits its limit for the day, or has simply expired. The bank often approves the exact same card a day or two later. Stripe knows this, which is why it has built-in retry logic and recovery emails, grouped under a feature called Smart Retries (part of "dunning").

The catch is that these are settings, not defaults. When your AI agent wired up Stripe, it built the happy path: card succeeds, access granted. You never asked "what happens when the card fails on renewal three weeks from now," so the agent never built it. The subscription just lapses, no email goes out, and you find out when the customer emails asking why they lost access, if they bother at all.

How to check

You want to know two things: does your code react to a failed payment, and does Stripe try to recover it.

  1. In your Stripe Dashboard, go to Settings, then Billing, then Revenue Recovery (older accounts: Subscriptions and emails). Look at whether Smart Retries and customer emails for failed payments are on. If they are off, no automatic recovery is happening.
  2. Search your codebase for the events invoice.payment_failed and payment_intent.payment_failed. If your webhook handler does not mention them, your app is deaf to declines. It only hears success.
  3. Trigger a real decline in test mode with card 4000 0000 0000 0341 (attaches fine, then fails on charge). Watch whether anything in your app or inbox responds. Silence means you are losing these customers today.

The fix

  1. Turn on Smart Retries and recovery emails in Stripe. This is the biggest win and it is a settings toggle. Stripe will retry the card on an optimised schedule and email the customer a link to update their payment method. Enable it before writing any code.
  2. Set a retry window and a clear endpoint. In the same settings, choose how long Stripe keeps retrying (for example, up to one week) and what happens when it gives up: cancel the subscription, or mark it unpaid. Decide deliberately, do not leave it on the default.
  3. Handle invoice.payment_failed in your webhook. When it fires, keep the account active during the retry window but flag it, and show a banner in your app: "Your last payment failed, update your card." Do not revoke access on the first failure. That is what the retry window is for.
  4. Handle the recovery. When invoice.payment_succeeded (or customer.subscription.updated back to active) fires, clear the flag and the banner. When Stripe exhausts retries and the subscription becomes canceled or unpaid, that is when you revoke access.
  5. Send customers to Stripe's hosted update page. Use a Billing Portal link so they can fix their card themselves. Do not build a form to re-collect card details, that pulls you into PCI scope you do not want.

The trap to avoid

The common wrong move is cutting off access the instant one payment fails. That punishes customers for a bank's fraud filter and turns a two-day blip into a cancelled account. The opposite trap is doing nothing and hoping: no retries, no email, no banner, so the customer never learns their card lapsed. Recovery lives in the middle. Keep them in, tell them clearly, give the card a few days.

Where this fits

Failed-payment recovery is invisible in every demo because demos use cards that succeed. It only shows up weeks after launch, as quiet churn you cannot explain. The free Readiness Report checks whether your Stripe flow reacts to declines at all, and the Finishing Pass wires up the retry, the banner, and the revoke-on-final-failure logic in order. If your webhook is silent on failures, it is probably silent on successes too, see paid but nothing happened and canceled but still has access.