Stripe Subscription Past Due: How to Handle It (And Recover Revenue)
A Stripe subscription enters past_due when a payment fails and retries haven't succeeded yet. It's a limbo state; the subscription is still alive, but the latest invoice is unpaid. Your app may still be granting access. The fix: audit all past_due subscriptions, retry invoices or send card update links, and set up automated dunning so past_due subscriptions get recovered before they become canceled.
Why Subscriptions Enter Past Due
A subscription moves to past_due when its latest invoice payment fails. This triggers Stripe's retry cycle. up to 4 automatic retries over ~3 weeks (with Smart Retries). During this window, the subscription stays in past_due status. The customer's previous billing period has ended, but the new period's payment hasn't been collected.
The past_due state is dangerous because it's ambiguous. The subscription isn't canceled. it's still technically active in Stripe's system. But the customer hasn't paid. Whether they still have access to your product depends entirely on your app's logic. Many SaaS apps don't check subscription status at all, which means past_due customers get free access for weeks; a direct cause of involuntary churn.
After all retries are exhausted, Stripe takes one of three actions based on your recovery settings (Dashboard > Settings > Subscriptions and emails > Manage failed payments): cancel the subscription, mark it as unpaid (access revoked but subscription exists), or leave it as past_due indefinitely. The default is to cancel, which means permanent customer loss if you haven't intervened.
Warning: Subscriptions stuck in past_due are your highest-priority recovery opportunity. These customers haven't churned yet. they're in a grace period. Every day you don't act, the probability of recovery drops. After 21 days, recovery rates fall below 10%. Act within the first 3 days for the best results.
Quick Fix: Audit and Recover Past Due Subscriptions
If you have subscriptions stuck in past_due right now, here's how to audit them and take immediate action:
Query all past_due subscriptions via the Stripe API to get a full list with decline codes and dollar amounts.
For each subscription, check the latest invoice's decline code to determine if it's a soft decline (retryable) or hard decline (needs customer action).
For soft declines (insufficient_funds, processing_error): manually retry the invoice via the API. The customer's bank may have resolved the issue.
For hard declines (expired_card, card_not_supported): create a Billing Portal session and send the card update link to the customer.
Decide on access gating: should past_due customers retain product access? If yes, set a grace period (e.g., 7 days). If no, gate access immediately by checking subscription.status in your app.
// Get all subscriptions currently in past_due status
const pastDueSubs = await stripe.subscriptions.list({
status: 'past_due',
limit: 100,
expand: ['data.latest_invoice'],
});
for (const sub of pastDueSubs.data) {
const invoice = sub.latest_invoice;
const error = invoice?.last_payment_error;
const amount = sub.items.data[0]?.price?.unit_amount / 100;
console.log(`Subscription: ${sub.id}`);
console.log(` Customer: ${sub.customer}`);
console.log(` MRR: ${amount}`);
console.log(` Decline code: ${error?.decline_code || 'none'}`);
console.log(` Past due since: ${new Date(sub.current_period_start * 1000).toISOString()}`);
console.log('---');
}
console.log(`\nTotal past_due: ${pastDueSubs.data.length} subscriptions`);// Option A: Retry the invoice (for soft declines)
try {
const invoice = await stripe.invoices.pay('in_xxx');
console.log(`Invoice ${invoice.id}: ${invoice.status}`);
// "paid" = success, subscription moves back to "active"
} catch (err) {
console.log(`Retry failed: ${err.message}`);
}
// Option B: Send card update link (for hard declines)
const session = await stripe.billingPortal.sessions.create({
customer: 'cus_xxx',
return_url: 'https://yourapp.com/account',
});
console.log(`Card update link: ${session.url}`);
// Send this URL to the customer via email
// Option C: Send the invoice directly to the customer
const sentInvoice = await stripe.invoices.sendInvoice('in_xxx');
console.log(`Invoice sent: ${sentInvoice.hosted_invoice_url}`);
// Customer receives email with a hosted payment pageTip: Check your app's access control logic. If you're only checking whether a Stripe subscription exists (and not its status), past_due customers have full free access. Add a status check: only grant access when subscription.status === 'active' or subscription.status === 'trialing'.
Permanent Fix: Automated Past Due Recovery
Manually auditing past_due subscriptions doesn't scale. At $20K MRR with a 5% failure rate, you'll have 15-25 subscriptions cycling through past_due every month. Each one needs to be triaged (soft vs. hard decline), retried or sent a card update link, and followed up with escalating emails over 3 weeks. Here's how to automate the entire pipeline:
Step 1: Listen for the invoice.payment_failed webhook. This fires the moment a subscription enters past_due. Use it to trigger your recovery sequence immediately. don't wait for the customer to notice.
Step 2: Triage the decline code and send the right first dunning email. Soft declines get a "we're retrying, no action needed" email. Hard declines get a "please update your card" email with a one-click portal link. This single triage step doubles your recovery rate compared to a generic message.
Step 3: Schedule follow-up emails at day 3, 5, 7, 14, and 21. Each email escalates urgency. The day 14 email should mention that access will be revoked. The day 21 email should be a final cancellation warning. Include the card update link in every single email.
SaveMRR automates all of this for $19/mo. The moment a subscription enters past_due, SaveMRR triggers a 7-email dunning sequence tailored to the decline type. It includes one-click card update links, tracks recovery in real-time, and alerts you when a customer updates their card. The first $200 recovered free.
// Listen for payment failures to catch past_due subscriptions
app.post('/webhooks/stripe', async (req, res) => {
const event = stripe.webhooks.constructEvent(req.body, sig, secret);
if (event.type === 'invoice.payment_failed') {
const invoice = event.data.object;
const decline = invoice.last_payment_error?.decline_code;
const customer = await stripe.customers.retrieve(invoice.customer);
// Generate card update link
const portal = await stripe.billingPortal.sessions.create({
customer: invoice.customer,
return_url: 'https://yourapp.com/account',
});
// Triage: soft vs hard decline
const isSoftDecline = ['insufficient_funds', 'processing_error']
.includes(decline);
// Send immediate email based on decline type
await sendEmail({
to: customer.email,
template: isSoftDecline ? 'soft_decline_notice' : 'card_update_needed',
data: {
name: customer.name,
amount: invoice.amount_due / 100,
cardUpdateUrl: portal.url,
},
});
// Schedule follow-up dunning sequence
await scheduleDunningEmails(invoice.customer, invoice.id, portal.url);
}
// Catch successful recovery
if (event.type === 'invoice.paid') {
const invoice = event.data.object;
// Cancel any remaining dunning emails
await cancelDunningSequence(invoice.customer, invoice.id);
console.log(`Recovered: ${invoice.amount_paid / 100} from ${invoice.customer}`);
}
res.json({ received: true });
});
// SaveMRR handles all of this. plus email templates,
// retry optimization, and real-time recovery tracking.Tip: Past_due subscriptions are your warmest recovery leads. These customers are already paying you. They just hit a card issue. Recovery rates within the first 3 days are 50-60%. By day 14, they drop to 15-20%. By day 21, under 10%. Speed matters. Run a free Revenue Scan to see how many past_due subscriptions you have right now.
Related Stripe Billing Issues
Frequently Asked Questions
What's the difference between past_due, unpaid, and canceled in Stripe?
past_due means the latest payment failed but retries are still being attempted; the subscription is alive and the customer may still have access. unpaid means all retries were exhausted but the subscription wasn't canceled. it's frozen (no access, but recoverable if the customer pays). canceled means the subscription is permanently ended. You configure which end-state Stripe uses in Dashboard > Settings > Subscriptions and emails > Manage failed payments.
Should I gate product access for past_due subscriptions?
It depends on your product. For mission-critical tools (hosting, infrastructure), gating access immediately motivates faster card updates but risks customer anger. For non-critical SaaS, a 7-14 day grace period is standard. It gives the customer time to update their card without feeling punished. Either way, always send a clear email explaining the situation and providing a one-click card update link.
How long does a subscription stay in past_due before Stripe takes action?
With Smart Retries, Stripe retries for approximately 3 weeks (the exact timing varies by ML model). With a fixed retry schedule, it depends on your configured intervals. After all retries are exhausted, Stripe executes your configured end-state action: cancel, mark as unpaid, or leave as past_due. The default is to cancel the subscription, which is permanent and cannot be undone.
Can I automatically resolve a past_due subscription without customer action?
Only if the decline is temporary (soft decline). Stripe's Smart Retries will automatically re-attempt the charge at optimal times, and if the bank approves it, the subscription moves back to active. But for hard declines (expired card, card not supported), no amount of retrying will work; the customer must update their payment method. That's why dunning emails with card update links are essential.
How do I prevent subscriptions from entering past_due in the first place?
Two strategies: (1) Pre-dunning card expiry alerts. email customers 30 days before their card expires using the customer.source.expiring webhook. This prevents 25-30% of payment failures. (2) Collect backup payment methods. If the primary card fails, Stripe can automatically charge a backup. Enable this in Dashboard > Settings > Subscriptions > Automatic collection. SaveMRR handles pre-dunning alerts automatically.
SaveMRR catches these automatically
Stop firefighting Stripe billing issues manually. Paste your API key, get a free Revenue Scan in 60 seconds, and let SaveMRR handle recovery automatically.
Run my free scan