Stripe failed payment retry strategy comes down to two paths: enable Stripe Billing Smart Retries for a set-and-forget baseline, or run custom dunning that listens to invoice.payment_failed and coordinates email or SMS plus timed follow-ups. We have built both patterns in production. This guide shows how we decide, how we implement, and what to watch for.
Definition: Stripe failed payment retry is the set of automated or manual actions you take after an invoice charge fails to recover revenue and reduce involuntary churn.
The problem it solves
Missed renewals are rarely a hard no. They are more often card issues that resolve with the right timing and a clean customer prompt. Manually chasing each failure scales poorly and causes overlaps with the next billing cycle if you get the timing wrong.
| Process | Manual handling | Automated handling |
|---|---|---|
| Detect failures | Staff reviews Dashboard each morning | Webhook on invoice.payment_failed fires instantly |
| Retry timing | Calendar reminders and ad hoc attempts | Smart Retries or a scheduler applies planned windows |
| Customer prompts | One-off emails, no links | Branded prompts with secure links, tracked by event |
| 3DS and special cases | Easy to miss action required | Targeted messages that request authentication |
| Stop conditions | Human judgment, error-prone | Idempotent state: stop on payment success or excessive retries |
| Reporting | Manual CSV pulls | Scheduled reports plus event logs consolidated |
How the automation works
In production we start with Stripe's Smart Retries when a client uses Stripe Billing and needs fast, low-touch recovery. We switch to, or layer in, custom dunning when we need channel control, bespoke timing, CRM coordination, or stricter stop rules. Both depend on Stripe's events and webhooks.
- Smart Retries: Enable in Stripe Billing. Stripe schedules ML-optimized retries and can send hosted emails or links. Note that some failures are not auto-retried. 3D Secure authentication failures require a customer prompt. Also tune retry windows so they do not overlap the next invoice. Excessive retries can be blocked by card networks.
- Custom dunning: Listen to invoice.payment_failed and related events via webhooks. Orchestrate your own outreach cadence across email and optionally SMS, write to your CRM, and schedule optional manual retry attempts. Avoid double retries if Smart Retries is also enabled.
- Events and auth: All API calls go to https://api.stripe.com and authenticate with API keys using HTTP Basic Auth. Webhooks deliver near real time event payloads for failures and recoveries. The Events API lets you review a feed for reconciliation.
- Reporting: Use Stripe's CSV exports or scheduled reports for daily reconciliation. Data Pipeline and the Reports API provide programmatic delivery at scale.
Step-by-step: how to build it
1) Decide Smart Retries vs custom
If you are on Stripe Billing and you do not need multi-channel control, enable Smart Retries in the Dashboard. If you need channel control or CRM coordination, run a custom flow listening to events and sending your own prompts. Avoid running two independent schedulers at once.
Decision rule:
- Start with Smart Retries when you want ML-optimized timings and Stripe-hosted recovery UX.
- Add custom dunning when you need email or SMS control, custom windows, CRM tasks, or agent routing.
- If both are on, only one component should schedule retries. Outreach can still run in parallel.2) Create a webhook endpoint for failures and recoveries
Receive events from Stripe and store event IDs so you never process an event twice. Use HTTPS and verify signatures per Stripe docs.
// server.js
import express from "express";
const app = express();
app.use(express.json({ type: "application/json" }));
// naive in-memory store for example only
const seen = new Set();
app.post("/webhooks/stripe", async (req, res) => {
const evt = req.body; // verify signatures in production
if (seen.has(evt.id)) return res.status(200).send("dup");
seen.add(evt.id);
if (evt.type === "invoice.payment_failed") {
const invoice = evt.data.object;
// enqueue outreach job keyed by invoice.id and customer.id
}
if (evt.type === "invoice.payment_succeeded") {
const invoice = evt.data.object;
// cancel any pending outreach for this invoice/customer
}
return res.status(200).send("ok");
});
app.listen(3000);Key gotcha: retry delivery means your endpoint can receive the same event multiple times. Make your handler idempotent by event.id.
3) Build a recovery scheduler that stays inside the billing period
Set a short, finite window so you do not retry after the next invoice finalizes. Store the plan on first failure and stop on success or manual write-offs.
// recovery-plan.js
const plans = new Map(); // invoiceId -> plan state
export function planOutreach(invoiceId, now = new Date()) {
if (plans.has(invoiceId)) return plans.get(invoiceId);
const steps = [
{ t: 0, kind: "email", template: "failed-1" },
{ t: 24, kind: "email", template: "update-card" },
{ t: 72, kind: "sms", template: "last-reminder" }
];
const schedule = steps.map(s => ({ ...s, at: new Date(now.getTime() + s.t * 3600_000) }));
const state = { status: "active", schedule };
plans.set(invoiceId, state);
return state;
}
export function stopOutreach(invoiceId) {
const s = plans.get(invoiceId);
if (s) s.status = "stopped";
}Keep the total duration below your billing interval to avoid overlap with the next cycle.
4) Send prompts that match the failure type
3D Secure failures are not automatically retried by settings. You must prompt the customer to authenticate. For generic insufficient funds, a simple reminder may be enough. Use templates keyed by failure reason and do not include sensitive data.
// notifier.js
import nodemailer from "nodemailer";
export async function sendEmail(to, subject, html) {
const t = nodemailer.createTransport({ sendmail: true });
await t.sendMail({ to, from: "billing@yourco.com", subject, html });
}
export function template(kind, context) {
if (kind === "failed-1") return `We could not process your renewal. Please update your card.`;
if (kind === "update-card") return `Your subscription is on hold. Update payment details to avoid interruption.`;
if (kind === "3ds") return `Please complete authentication to finish your payment.`;
return `Please update your payment method.`;
}5) Coordinate with Smart Retries to avoid double attempts
If Smart Retries is on, let Stripe schedule charge attempts and have your scheduler send prompts only. If you disable Smart Retries, your team can trigger a retry manually or via API when appropriate. Avoid overlapping automated attempts.
Coexistence rule:
- Smart Retries on: your flow sends messages, Stripe schedules attempts.
- Smart Retries off: your flow can schedule a manual attempt after a prompt.
- Never run two automated schedulers that both attempt charges.6) Reconcile with Stripe reports and the Events API
Use daily CSV exports or scheduled reports from the Dashboard for an accounting view. The Events API lets you page invoice.payment_failed and invoice.payment_succeeded for an operational view. Reconcile outreach jobs to recovered payments for a feedback loop.
# Example: authenticate with HTTP Basic and list events by type (shape simplified)
curl https://api.stripe.com/v1/events \
-u sk_live_xxx: \
-G --data-urlencode "type=invoice.payment_failed"Where it gets complicated
- 3DS action required: Retry settings do not solve 3D Secure authentication failures. You must message the customer to authenticate. If you skip this, the payment will not succeed.
- Overlap with next invoice: Long retry windows can collide with the next billing period. Keep your recovery duration shorter than the subscription interval.
- Excessive retries: Card networks can block after too many attempts. Stripe surfaces protections for excessive retries. Respect those limits.
- Double-retry conflicts: Running Smart Retries and a custom retry scheduler together risks back-to-back attempts. If you keep Smart Retries on, let Stripe handle attempts and keep your system on messaging only.
- Webhook idempotency and reliability: Stripe can deliver the same event more than once. Persist event IDs and make handlers idempotent so you never send two emails for one failure.
What this actually changes
For a B2B subscription product we support, Smart Retries covered the easy wins immediately. We added a custom dunning layer to control channels and timing, route high-value accounts to a human, and stop before the next cycle. That combination reduced back-and-forth in support and made recoveries predictable even during card network outages.
One external benchmark to calibrate expectations: Recurly's Subscription Benchmark Report has reported that dunning and retries can recover roughly a third of failed renewals on average across subscription businesses. Source: https://recurly.com/research/report/subscription-benchmarks/.
Frequently asked questions
Should I just enable Smart Retries and call it a day?
If you are on Stripe Billing and do not need channel control, Smart Retries is a strong default. It schedules retries and can handle recovery UX. You still need to cover cases like 3D Secure authentication that require customer action. If you need email or SMS control or CRM tasks, add a custom layer.
Can I run Smart Retries and custom dunning together?
Yes, but you must prevent double attempts. The safe pattern: keep Smart Retries for charge timing and have your custom flow handle messaging and CRM. If you prefer your own attempt timing, disable Smart Retries and keep one scheduler in charge.
How do I avoid retries spilling into the next billing period?
Keep your recovery window shorter than the subscription interval and stop on success. Stripe warns that long retry schedules can overlap the next invoice. Tune durations accordingly and cancel pending outreach when invoice.payment_succeeded arrives.
Do 3D Secure failures get auto retried?
No. 3DS authentication failures require customer action. Your flow should detect the reason and prompt the customer to authenticate. Without that, subsequent attempts will not clear.
What does a custom dunning setup cost monthly?
Stripe webhooks and reports are included in Stripe. Your costs are the small service that listens to events, email or SMS send costs, and optional automation tooling like Zapier or Make. Build effort is the primary cost. We scope these based on volume and channel needs.
Can a non-developer set this up?
Enabling Smart Retries is a Dashboard setting. Building a reliable webhook-driven flow with idempotency, channel logic, and CRM coordination is development work. We ship this as a small service with clear handoff and runbooks.
If you want a second set of eyes on your recovery flow, we have built both Smart Retries and custom dunning in production. See our related guide on the webhook pattern in Stripe at [/blog/stripe-invoice-payment-failed-webhook-guide]. For help implementing, see our workflow automation services and book a call.
Want us to build this for you?
15-minute discovery call. No pitch. We tell you what to automate first.
Book a Discovery Call