Skip to content

Payments & SaaS

Stripe Subscription Dunning — Automating Recurring Payments & Failed-Card Recovery

Beyond buy-once: Velocity X customers can add $49/mo hosting + support subscriptions. Stripe's dunning engine handles failed cards and keeps cash flowing.

💳 🔄 💜

Spoiler: if you're selling monthly subscriptions, a customer's card is going to decline eventually. Someone's AMEX gets flagged for fraud, a corporate card gets revoked, a payment processor glitches — it happens 7–10% of the time across SaaS. Stripe's dunning system is Stripe's answer: automate retry logic, email the customer, suspend their subscription after three failures, and let them resume when they update their card. Velocity X uses it for hosting ($49/mo) and support add-ons; it's recovered more revenue than any single product decision we've made. Boring infrastructure that prints money.

What Is Dunning (and Why You Need It)?

Dunning is the process of recovering failed recurring payments. When a subscription payment fails, dunning says: retry in 3 days, retry again in 5 days, email the customer each time, and if it fails 3 times total, pause the subscription. No manual intervention. No customer support tickets. Just automatic cash recovery running in the background. For SaaS with thousands of subscribers, dunning is the difference between 88% net revenue retention and 81% — that's real money.

Without dunning, a single failed payment kills a subscription forever. You'd have to detect the failure, email the customer manually, wait for them to reply, update their payment method, and restart billing. Most customers don't respond to one email, so you lose the revenue. Dunning automates that entire flow and recovers 30–50% of otherwise-lost subscriptions.

Stripe's Dunning Engine — Configuration & Defaults

Stripe's dunning is built into Subscriptions and configured in the Dashboard under "Billing Settings." You define: how many retry attempts (Stripe recommends 3–4), how many days between retries (3, 8, 14 days is standard), email templates for each attempt, and what happens after final failure (Stripe defaults to pausing the subscription). You don't write code; you configure the strategy once, and Stripe executes it for every subscription automatically.

Velocity X uses Stripe's default dunning strategy: fail on day 1, retry day 3 (customer gets email: "Your payment failed. Update your card."), retry day 8 (second email), fail permanently and pause subscription day 14. At pause, the customer sees their subscription as "Paused" in their dashboard, can update their card anytime, and resumption is instant — no manual re-provisioning needed.

// Dunning config lives in Stripe Dashboard, not code // But here's how to listen for dunning events via webhook: {"\n"} {"\n"}app.post('/webhook', (req, res) => {"{"} {"\n"} const event = req.body; {"\n"} {"\n"} if (event.type === 'invoice.payment_failed') {"{"} {"\n"} const invoice = event.data.object; {"\n"} // Stripe is retrying automatically; customer got email {"\n"} console.log(Dunning in progress for subscription ${invoice.subscription}); {"\n"} {"}"} {"\n"} {"\n"} if (event.type === 'customer.subscription_updated') {"{"} {"\n"} const subscription = event.data.object; {"\n"} if (subscription.status === 'paused') {"{"} {"\n"} // Subscription paused after failed dunning {"\n"} console.log(Subscription paused: ${subscription.id}); {"\n"} {"}"} {"\n"} {"}"} {"\n"}{"}"});

You listen for webhook events (invoice.payment_failed, invoice.payment_succeeded, customer.subscription_updated), but you don't send the retries yourself. Stripe owns that loop.

Why Dunning Works — The Math

Imagine 100 monthly subscriptions at $49/mo = $4,900 MRR. Each month, 7 cards fail. Without dunning, you lose 7 customers instantly (−$343 MRR). With dunning, Stripe retries those 7 cards three times over 2 weeks. Of the 7, 3 fail permanently (customer updates their card later) and 4 succeed on retry. You've recovered $196/mo of what would have been lost revenue. Over a year, that's $2,352 — more than the annual cost of Stripe's infrastructure for that feature. And that's a 100-customer cohort. At 1,000 customers, it's $23K+ per year.

Stripe's default dunning strategy recovers 30–50% of failed payments across thousands of SaaS companies. Some companies tune it (more/fewer retries, longer gaps) and see even better recovery. The point is: dunning is not a nice-to-have, it's a revenue lever equivalent to changing your churn rate.

Pause & Resume — Keeping Customers in the Loop

When dunning exhausts and a subscription pauses, the customer still exists in your system. Their data, their seat, their history — all intact. Pause is a state, not a deletion. The customer gets an email saying "Your subscription is paused. Update your payment method anytime to resume." They log into their dashboard, click "Update Payment", Stripe's card form appears, they paste a new card, and the subscription reactivates instantly. No new setup. No re-provisioning. No customer support tickets.

Velocity X tracks pause events (customer.subscription_updated with status === 'paused') and could send a follow-up email or in-app notification after 24 hours (e.g. "Your hosting is paused; click here to resume"). But the default behavior — let customers self-serve — works fine. Most customers who pause their subscription are busy and will fix it when they remember. Don't nag them.

Combining Subscriptions with One-Off Charges

Velocity X sells fixed-tier packages ($4,995 to $24,995) via Payment Links, then offers monthly hosting ($49/mo) as an add-on subscription. A single customer might have: one Payment Link charge (initial package) + one Subscription (monthly hosting) + another Subscription (support plan). They're all on the same Stripe Customer record, so you can query total lifetime value in one call. Dunning applies to both subscriptions independently; one subscription pausing doesn't affect the other.

Webhook Listening — The Minimal Integration

To make dunning useful, listen for three webhook events: invoice.payment_failed (dunning started), invoice.payment_succeeded (retry worked), and customer.subscription_updated (pause or resume). Update your local database with the subscription status, and use that to disable customer access (pause → no login) or re-enable it (resume → login works again). You don't orchestrate the retries; Stripe does. You just react.

// Listen for pause/resume and sync to your db {"\n"} {"\n"}if (event.type === 'customer.subscription_updated') {"{"} {"\n"} const sub = event.data.object; {"\n"} {"\n"} if (sub.status === 'paused') {"{"} {"\n"} // Disable access for this customer {"\n"} await db.update('customers') {"\n"} .set({ access_enabled: false }) {"\n"} .where({ stripe_subscription_id: sub.id }); {"\n"} {"}"} else if (sub.status === 'active') {"{"} {"\n"} // Re-enable access {"\n"} await db.update('customers') {"\n"} .set({ access_enabled: true }) {"\n"} .where({ stripe_subscription_id: sub.id }); {"\n"} {"}"} {"\n"}{"}"});

Frequently Asked Questions

What happens if a customer updates their card mid-dunning?

If they update their card while retries are pending, Stripe immediately retries the next invoice with the new card. If it succeeds, subscription returns to active. If dunning is already in "paused" state and they update their card, their subscription automatically resumes (depending on your Stripe settings — most default to auto-resume). Always test this flow with a test card in Stripe's Dashboard.

Can I customize the dunning email templates?

Yes. Stripe's Dashboard → Billing Settings → Email Templates. You can edit the subject and body for each retry attempt and the final pause email. Keep it clear and friendly; include a link to their dashboard where they can update their payment method. Don't make them hunt for where to fix it.

What's the difference between pausing and cancelling?

Pause is temporary — the customer's seat, history, and data remain intact. They resume anytime by updating their card. Cancel is permanent — the subscription ends, you might delete their account, and they'd have to sign up fresh to come back. Pause is customer-friendly; use it as the default end-state for dunning. Cancel only when a customer explicitly asks.

How do I handle tax on recurring subscriptions?

Stripe Tax integrates with Subscriptions just like Payment Links. Configure the subscription with automatic_tax: true, and Stripe calculates GST/VAT per invoice based on the customer's address and tax jurisdiction. Each invoice has the correct tax line item. Invoicing is automatic; tax is correct. That's it.

What if a customer wants to change their subscription tier?

Use Stripe's "Update Subscription" API to change the price_id to a new tier. Stripe prorates the charge — if they upgraded mid-month, they pay the difference; if they downgraded, they get a credit. Handle the proration setting in the API call (proration_behavior: 'create_prorations'). Update your dashboard to show the new tier, and you're done.

Should I email the customer if dunning fails and subscription pauses?

Stripe sends a "payment failed" email and a "subscription paused" email automatically. You don't need to send another one. But you could send a follow-up after 3 days (e.g., "Still paused? Update your card anytime at [dashboard link]"). Keep it light; respect that they're busy and will fix it when they remember.

The Bottom Line

Dunning is the invisible revenue recovery system that makes SaaS sustainable. Without it, you'd lose 7–10% of recurring revenue to failed cards every month. Stripe's dunning is automatic, configurable, and tested across millions of subscriptions. Pause/resume is customer-friendly and keeps churn low. Listen for webhook events, sync subscription status to your database, and let Stripe's infrastructure run the retry loop. It's the kind of "boring" infrastructure that compounds into serious money. For Velocity X, hosting subscriptions are 20% of MRR, and dunning protects nearly all of it. Browse our hosting tiers and add one to your package — start collecting that recurring revenue today.

Let us make some quick suggestions?

Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.