Someone stops paying. You assume they churned. Then you look closer and find the card declined, the bank blocked a recurring charge, or the expiry date rolled over and nobody told you. That is involuntary churn: the customer still wants the product, but the payment failed. Under about 200 customers, you can recover a lot of it with Stripe Billing's Smart Retries, a one-click card update link, a calm three-email sequence, and a clear rule for what happens while the subscription is past due. This post is about failed payments, not cancel screens or "why did they leave" interviews.
Quick answer
Turn on Stripe Smart Retries (Stripe's recommended default is 8 tries within 2 weeks) under Billing > Revenue recovery > Retries. Enable Stripe's failed-payment customer emails, and point the update link at the Customer Portal (or a deep link with payment_method_update). Add your own three emails: day 0 (failed, here is the fix), mid-retry (still failing, soft decline vs expired card), and final (access will pause soon). Keep product access while the subscription is past_due, then pause rather than hard-cancel on the first miss. Put a small in-app billing banner on every screen. Measure Failed → recovered in 7 days. Do this before you rewrite onboarding or redesign cancel flows.
Failed payment vs cancel (voluntary)
Two different problems get lumped under "churn":
Voluntary churn: the customer cancels, or stops using the product and later cancels. Fix with product, support, and a fair cancel flow.
Involuntary churn: the charge fails. The subscription moves toward
past_due(and laterunpaid,canceled, orpaused, depending on your Stripe settings). Fix with retries, an easy card update, and clear emails.
If someone is trying to leave on purpose, that is voluntary cancel work, not dunning. Aura++ already covers how to design a SaaS cancel flow that reduces churn without dark patterns. Early product churn under 50 customers is a third problem (activation and fit), separate from both. This guide assumes they intended to stay and the card said no.
Illustrative example (made-up): Ledgerly is a $39/month bookkeeping SaaS with 120 paying customers. Last month, 7 renewals failed. Three recovered after Stripe retried. Two updated their card from an email. One was a hard decline (stolen card code) and needed a new method before any retry would run. One never replied and hit the pause rule. Without a recovery path, all seven would have looked like "churn" in a spreadsheet.
Turn on Smart Retries first
Before you write witty emails, let Stripe retry the invoice. Many declines are temporary: insufficient funds that clear in a few days, a bank soft decline, a network blip. Stripe Billing can retry those for you.
In the Dashboard, go to Billing > Revenue recovery > Retries. Stripe recommends Smart Retries, which picks retry times using signals about when a payment method is more likely to succeed. You set how many retries and the maximum window: 1 week, 2 weeks, 3 weeks, 1 month, or 2 months. Stripe's documented recommended default is 8 tries within 2 weeks.
What Smart Retries will not do:
Retry when no payment method is available.
Retry after certain hard decline codes (for example lost or stolen card, incorrect number, authentication required). Scheduled retries keep showing up, but they only run after you get a new payment method.
Retry India-issued cards under Stripe's documented exception list, or a disconnected Connect account.
Listen for invoice.payment_failed. The attempt_count field tells you how many attempts have happened. The invoice's next_payment_attempt tells you when Stripe plans to try again (automations users should also watch invoice.updated).
After retries are exhausted, configure the outcome carefully. Stripe lets you cancel the subscription, mark it unpaid, leave it past due, or pause eligible subscriptions. For early SaaS, my rule of thumb is: do not cancel on the first failed renewal. Prefer leave past due or pause so a recovered payment can restore access without a brand-new checkout.
Source: Stripe's Smart Retries docs (checked 9 October 2026).
Customer Portal and a one-click update link
Retries only help if the card can eventually succeed. When the card is expired, replaced, or hard-declined, the customer must add a new method. Make that one click, not a scavenger hunt through Settings.
Activate the Customer Portal in the Stripe Dashboard and allow payment method updates.
Point Stripe's failed-payment emails at the portal (or your own billing page). Under revenue recovery / customer email settings, enable sending emails when card payments fail. Those emails include a link to update the payment method.
Use a portal deep link when you send your own email or in-app CTA. Create a Billing Portal session with
flow_data[type]=payment_method_update. That opens straight to "add a payment method" and sets it ascustomer.invoice_settings.default_payment_method. Hide the rest of the portal chrome so they finish one job.
Two implementation details that bite people:
If the failed charge used the subscription's
default_payment_method, updating only the customer's invoice default may not be enough. Update the field Stripe is actually retrying against.Portal sessions are short-lived. Generate a fresh link when you send the email or when they click in-app. Do not paste a week-old portal URL into a template.
Sources: Stripe Customer Portal overview, portal deep links, and customer emails for revenue recovery (checked 9 October 2026).
A calm 3-email sequence (day 0 / mid / final)
Stripe's built-in failed-payment email is a good baseline. Under ~200 customers, add your own short sequence so the tone matches your product and you can branch on soft vs hard declines. Keep it factual. No guilt, no fake urgency meters, no "we miss you" copy for a billing glitch.
Here is a sketch for Ledgerly (made-up). Treat the wording as a starting point, not a benchmarked template.
Email 1: Day 0 (payment failed)
Subject: Ledgerly payment failed: update your card in one minute
Body sketch:
We could not charge $39 for your Ledgerly subscription on {date}.
Your account is still open while we retry.
Button: Update payment method (portal deep link).
If you already updated the card at your bank, you can ignore this. We will retry automatically.
Reply to this email if something looks wrong.
Email 2: Mid-window (still failing)
Send this around the middle of your retry window (for an 8-tries / 2-week Smart Retry policy, roughly day 5 to 7). Branch the first line on what you know:
Soft decline / insufficient funds style: "Our payment processor is still retrying. If funds were low, topping up the card is often enough. You can also switch to a different card here: {link}."
Expired card: "The card on file looks expired. Add a new card here: {link}. Retries cannot succeed until there is a valid method."
Hard decline (lost/stolen/incorrect number, etc.): "The bank rejected this card and automatic retries will not work until you add a new payment method: {link}."
Do not invent decline-rate statistics. Use the decline code Stripe gives you, mapped to plain language.
Email 3: Final notice before pause
Subject: Last step to keep Ledgerly access
Body sketch:
We still have not received payment for the open invoice.
On {date}, we will pause the subscription so you are not surprised by another charge. Your data stays. You can resume by updating the card.
Button: Update payment method.
If you meant to cancel, reply and we will close it cleanly. No dark patterns, no hostage screens.
For a tighter starter sequence focused only on email copy when you have under 50 customers, see the DEV post on failed payment emails when you have under 50 customers. This Aura++ guide adds the Stripe retry settings, portal deep links, access rules, and measurement around those emails.
Soft decline vs expired card (message, don't invent rates)
You do not need industry recovery percentages to write better emails. You need a branch:
What you see | What to tell the customer | What you do in product |
|---|---|---|
Temporary / soft failure; retries still scheduled | We are retrying. You can wait or update the card now. | Keep access. Show banner with "Retries in progress" + update link. |
Expired card (or Stripe expiring-card email already fired) | This card is expired. Add a new one. | Update link is the only useful CTA. Consider Stripe's expiring-card email one month ahead as prevention. |
Hard decline code Stripe will not auto-retry | The bank blocked this card. We need a new payment method before charges can succeed. | Stop implying "we'll keep trying the same card." Require a new method. |
No payment method on file | There is no card to charge. Add one to reopen billing. | Same as hard decline: portal update flow. |
Stripe can also email customers about expiring cards one month before the default card expires. Turn that on under email notifications. Prevention is quieter than recovery.
Keep access during past_due, then pause
When a renewal fails, the subscription often becomes past_due while Stripe retries. Decide what the product does in that window before the first failure hits production.
During past_due (retries still running): keep access on. Put a billing banner everywhere. Do not brick the product for a soft decline that may clear tomorrow.
After final retry: pause rather than cancel, if your Stripe settings and product support it. Pausing stops new invoices (depending on the pause path you choose) without deleting the customer relationship. Cancel is for "we're done." Failed card is usually "we're stuck."
Unpaid vs canceled: Stripe can mark the subscription unpaid or cancel it after the retry schedule. Unpaid keeps the subscription object but stops collection attempts; cancel is terminal for that subscription. Pick deliberately in Dashboard failed-payment settings.
This is status handling, not a cancel-flow redesign. You are not building countdown modals or "are you sure" mazes. You are saying: access continues while we recover payment; then we pause cleanly.
Stripe documents past_due as the state when payment on the latest finalized invoice failed or was not attempted, with retries possible depending on your settings. See the subscriptions overview status table if you need the exact definitions for your eng ticket.
In-app banner
Email is necessary and incomplete. People filter billing mail. An in-app banner catches them when they are already using Ledgerly.
Show it on every authenticated page while status is
past_dueor while an open invoice is unpaid.One sentence + one button: "Payment failed. Update your card to keep uninterrupted access." Button opens the portal
payment_method_updateflow.Do not block the whole UI on day 0. A full-screen paywall on the first soft decline trains people to ignore you or rage-cancel.
After the final notice date, you can switch the banner to "Account paused. Update payment to resume" and gate write actions if you must. Still keep a path back in.
Measure Failed → recovered in 7 days
If you only watch monthly churn %, involuntary and voluntary blur together. Add one recovery metric.
Definition (rule of thumb): of invoices that hit invoice.payment_failed for an active subscription renewal, what share become paid within 7 days (or within your Smart Retry window, if you prefer one number)?
Track weekly:
Failed renewals (count and $).
Recovered within 7 days (count and $).
Still open / paused / canceled after the window.
Recovery path: Stripe retry alone vs customer updated card vs you manually collected.
You do not need a warehouse. A spreadsheet fed by Stripe's invoice events is enough under 200 customers. When you outgrow that, fold Failed → recovered into the same lightweight metrics habit described in IndieHunt's guide on tracking the product metrics that matter before your first 100 users: pick one activation-style event (here, recovery) and review it on a fixed cadence.
14-day setup plan
Day | Work | Done when |
|---|---|---|
1 | Audit current Stripe retry settings and failed-payment outcomes | You know whether Smart Retries is on, try count/window, and cancel vs unpaid vs past_due vs pause |
2 | Set Smart Retries to 8 tries / 2 weeks (or a deliberate custom schedule) | Dashboard matches your written policy |
3 | Enable Stripe failed-payment emails + expiring-card emails; point links at Customer Portal | Test email in sandbox to a team address |
4 to 5 | Activate Customer Portal; build | Clicking the link updates the method Stripe retries |
6 to 7 | Write the three emails; map hard vs soft decline copy | Templates live in your ESP or Stripe automations with merge fields |
8 to 9 | Ship in-app billing banner for past_due | Banner visible on main app shell with working CTA |
10 | Define access policy: keep access during retries; pause after final failure | Eng + support share the same one-pager |
11 to 12 | Wire | Last 30 days of failures backfilled once |
13 | Support macros: how to reply, when to extend grace, when to refund confusion | Macros documented |
14 | Dry run: create a test customer, fail a payment, walk emails + banner + portal | Recovered test invoice; notes fixed |
Traps
Canceling on the first decline. You turn a recoverable miss into voluntary-looking churn.
"Update billing" buried in settings. If the fix takes five clicks, recovery emails underperform.
One generic email for every decline code. Hard declines need a new method message, not "we'll keep trying."
Retrying forever with no pause date. Customers feel blindsided when access finally dies. Name the date in email 3.
Bricking the app on day 0. Soft declines punish engaged users.
Measuring only logo churn. Without Failed → recovered, you "fix" product while cards expire.
Stale portal links in templates. Generate sessions when you send or when they click.
Updating the wrong default payment field. Stripe retries the method on the subscription first. Match that field.
Mixing this work with cancel-flow experiments. Different intent, different metrics, different copy.
FAQ
Should I use Stripe's emails, my own, or both?
Start with Stripe's failed-payment emails so every failure gets a portal link without eng work. Add your own three-email sequence when you want brand voice, decline-code branching, or a final pause warning with your product wording. Avoid sending two near-identical emails on the same day; stagger or disable overlap.
How long should the grace period be?
Align it with your retry window. If Smart Retries runs for two weeks, keep access for those two weeks, then pause. Longer grace is fine for annual plans or high-touch B2B; just write the date into the final email so nobody is surprised. This is a policy choice, not a universal benchmark.
What if the customer says they canceled the card on purpose?
Treat it as voluntary churn from that point. Offer a clean cancel or pause, do not keep dunning, and ask one short question about why if they are willing. Do not force them through a dark-pattern cancel maze because a payment failed first.
Wrap-up
Involuntary churn is a systems problem. Smart Retries buy you time. The Customer Portal and a payment_method_update deep link make the fix obvious. Three calm emails plus an in-app banner cover inbox and product. Keep access while status is past due, then pause. Measure Failed → recovered in 7 days so you know the loop works. Do that for Ledgerly-scale SaaS before you spend a month on growth experiments that cannot help an expired card.
If you are browsing launch and growth playbooks for early products, Aura++ is built for that crowd. Pair this recovery setup with honest cancel flows and early churn diagnosis when the problem is voluntary, not a declined charge.