Technical Guide
February 5, 2026·14 min read

Stripe Failed Payment Retry: Complete Technical Guide

Stripe's default retry logic recovers only 30% of failed payments. This technical guide shows you how Stripe retries work, what's missing, and how to build smarter retry logic that can achieve industry-leading recovery rates (85-94% based on studies).

How Stripe's Default Retry Logic Works

When a subscription payment fails in Stripe, the platform automatically attempts to retry the charge. Here's what happens:

Stripe's Default Retry Schedule

Default Retry Timeline

  • Day 0: Initial charge fails
  • Day 3: First retry attempt
  • Day 5: Second retry attempt
  • Day 7: Third retry attempt
  • Day 7+ (if all fail): Subscription marked as past_due or unpaid

Note: You can configure this schedule in Stripe Dashboard → Settings → Billing → Subscriptions and emails.

What Happens During a Retry

  1. 1. Payment attempt: Stripe attempts to charge the same card that failed previously
  2. 2. Webhook fired: invoice.payment_action_required or invoice.payment_failed
  3. 3. Email sent (optional): Stripe can email customers about the failure
  4. 4. Subscription state updated: Remains past_due if retry fails

The Problem with Stripe's Default Retries

Stripe's one-size-fits-all approach has major limitations:

  • Ignores decline codes: Retries expired cards the same way as insufficient funds (even though expired cards will never succeed)
  • Fixed schedule: Doesn't adapt to customer behavior (payday, billing cycles)
  • Limited attempts: Only 3-4 retries before giving up
  • No smart timing: Retries at the same time of day regardless of when the customer is likely to have funds
  • Poor recovery rate: Only 30-40% of failed payments are recovered

Understanding Stripe Decline Codes

When a payment fails, Stripe returns a decline code that explains why. This is your roadmap for building smarter retry logic.

Common Decline Codes & What They Mean

Decline CodeMeaningShould Retry?Action
card_declinedGeneric bank declineYesRetry in 4h → 24h → 3d
insufficient_fundsNot enough money in accountYesRetry after payday (3-7d)
expired_cardCard expiration date passedNoEmail customer immediately
incorrect_cvcCVC code is wrongNoEmail customer immediately
processing_errorNetwork/technical issueYesRetry quickly (1h → 4h → 24h)
do_not_honorBank blocked the chargeMaybeRetry once in 24h, then email
card_velocity_exceededToo many charges too fastYes, slowlyWait 7d before retrying
lost_card / stolen_cardCustomer reported card lost/stolenNoEmail customer for new card

How to Access Decline Codes

In the Stripe API, decline codes are returned in the charge object:

{
  "id": "ch_3ABC...",
  "object": "charge",
  "status": "failed",
  "failure_code": "insufficient_funds",
  "failure_message": "Your card has insufficient funds.",
  "outcome": {
    "type": "issuer_declined",
    "reason": "insufficient_funds"
  }
}

You can also view decline codes in the Stripe Dashboard → Payments → [Failed Payment] → "Decline reason".

Building Smart Retry Logic

To achieve the industry benchmark of 85-94% recovery (vs Stripe's 30%), you need decline code-aware retry logic.

Smart Retry Schedule by Decline Code

For insufficient_funds:

  • 1st retry: 3 days (wait for payday)
  • 2nd retry: 7 days (biweekly paycheck)
  • 3rd retry: 14 days (monthly billing cycle)
  • 4th retry: 30 days (last chance before cancellation)

Why it works: Aligns retries with when customers are likely to have funds

For card_declined (generic):

  • 1st retry: 4 hours (might be daily limit issue)
  • 2nd retry: 24 hours (next day, new limit)
  • 3rd retry: 3 days
  • 4th retry: 7 days

Why it works: Catches temporary bank limits and customer actions

For processing_error:

  • 1st retry: 1 hour (technical issue likely resolved)
  • 2nd retry: 4 hours
  • 3rd retry: 24 hours
  • 4th retry: 3 days

Why it works: Technical errors usually resolve quickly

For expired_card / incorrect_cvc:

DO NOT RETRY. Send dunning email immediately with card update link.

Why: No amount of retrying will succeed — customer must update their card

Implementation Options

You have three options for implementing smart retries:

Option 1: Configure Stripe's Retry Rules

In Stripe Dashboard → Settings → Billing → Subscriptions and emails, you can customize:

  • • Number of retry attempts (1-4)
  • • Days between retries
  • • Email notifications to customers

Pros: Easy, no code required
Cons: Still one-size-fits-all, doesn't adapt to decline codes

Option 2: Build Custom Retry Logic with Webhooks

Listen to Stripe webhooks and trigger manual retries based on decline codes:

// Example: Node.js webhook handler
app.post('/stripe-webhook', async (req, res) => {
  const event = req.body;
  
  if (event.type === 'invoice.payment_failed') {
    const invoice = event.data.object;
    const charge = invoice.charge;
    const declineCode = charge.failure_code;
    
    // Smart retry logic
    if (declineCode === 'insufficient_funds') {
      scheduleRetry(invoice.id, 3 * 24 * 60 * 60 * 1000); // 3 days
    } else if (declineCode === 'card_declined') {
      scheduleRetry(invoice.id, 4 * 60 * 60 * 1000); // 4 hours
    } else if (declineCode === 'expired_card') {
      sendDunningEmail(invoice.customer);
    }
  }
  
  res.status(200).send('OK');
});

Pros: Full control, adapts to decline codes
Cons: Requires development time, ongoing maintenance

Option 3: Use a Payment Recovery Tool (Like Revive)

Tools like Revive handle smart retries automatically:

  • ✅ Decline code-aware retry schedules
  • ✅ Automated dunning emails
  • ✅ Real-time recovery analytics
  • ✅ No code required

Pros: Zero setup, designed for industry-leading recovery rates, no engineering time
Cons: Monthly cost (but ROI is typically 10-50x)

Webhook Events You Need to Track

If you're building custom retry logic, listen to these Stripe webhook events:

EventWhen It FiresAction
invoice.payment_failedPayment attempt failedSchedule smart retry based on decline code
invoice.payment_succeededPayment succeeded (after retry)Mark as recovered, stop retry loop
customer.subscription.updatedSubscription status changedTrack if moved to past_due
charge.failedIndividual charge failedExtract decline code for analysis

Example Webhook Payload

{
  "type": "invoice.payment_failed",
  "data": {
    "object": {
      "id": "in_1ABC...",
      "customer": "cus_XYZ...",
      "subscription": "sub_123...",
      "amount_due": 2900,
      "attempt_count": 1,
      "charge": {
        "id": "ch_3DEF...",
        "failure_code": "insufficient_funds",
        "failure_message": "Your card has insufficient funds."
      }
    }
  }
}

Advanced Strategies

1. Vary Retry Time of Day

A charge that fails at 2 AM might succeed at 10 AM (when banks are more lenient with daily limits). Spread retries across different times:

  • 1st retry: 10 AM (morning when balances are higher)
  • 2nd retry: 2 PM (afternoon)
  • 3rd retry: 8 PM (evening after work hours)

2. Enable Stripe's Network Tokens

Network tokens automatically update expired card details and increase authorization rates. Enable in Stripe Dashboard → Settings → Payments → Network tokens.

Impact: Reduces expired card failures by 50%+

3. Use Card Account Updater

Visa and Mastercard automatically provide new card details when cards expire. Enable in Stripe Dashboard → Settings → Billing → Card account updater.

Impact: Reduces expired card failures by 30-40%

4. Retry with Backup Payment Methods

If you've collected multiple payment methods from customers, try the backup method when the primary fails:

// After primary card fails
if (customer.backup_payment_method) {
  stripe.invoices.pay(invoice.id, {
    payment_method: customer.backup_payment_method
  });
}

Dunning Management with Stripe

Payment retries alone won't recover expired cards or persistent failures. You need dunning emails to prompt customer action.

Stripe's Built-In Dunning Emails

Stripe can send automatic emails when payments fail. Configure in Dashboard → Settings → Billing → Emails:

  • ✅ Payment failed notification
  • ✅ Upcoming invoice reminders
  • ✅ Subscription canceled notification

Limitation: Stripe's emails are generic and not customizable. They have low open rates (~13%) and poor conversion.

Custom Dunning Emails (Better Approach)

Disable Stripe's default emails and send your own branded emails:

  1. 1. Disable Stripe emails: Dashboard → Settings → Billing → Emails → Turn off
  2. 2. Listen to webhooks: invoice.payment_failed
  3. 3. Generate card update link: Use Stripe Checkout in setup mode
  4. 4. Send branded email: Via SendGrid, Postmark, etc.
// Generate card update link
const session = await stripe.checkout.sessions.create({
  mode: 'setup',
  customer: 'cus_ABC...',
  success_url: 'https://yourapp.com/billing/success',
  cancel_url: 'https://yourapp.com/billing'
});

// session.url is the one-click card update link
sendEmail({
  to: customer.email,
  subject: 'Payment update needed',
  body: `Click here to update: ${session.url}`
});

Monitoring & Analytics

Track these metrics to optimize your retry strategy:

  • Payment Failure Rate:
    (Failed payments / Total payments) × 100
    Benchmark: 7-11% is average
  • Recovery Rate:
    (Recovered payments / Failed payments) × 100
    Benchmark: 85-94% with smart retries
  • Average Time to Recovery:
    Days from failure to successful charge
    Benchmark: <7 days is ideal
  • Recovery by Decline Code:
    Which decline codes have the best/worst recovery rates?

Where to View Metrics in Stripe

  • Failed payments: Dashboard → Payments → Filter by "Failed"
  • Decline codes: Click on any failed payment → "Decline reason"
  • Recovery trends: Dashboard → Analytics → Payment retries (if available)

Skip the Engineering Work

Revive implements smart, decline code-aware retries and dunning emails for Stripe automatically. Connect your account in 3 minutes and start recovering more failed payments.

Try Revive Free

14-day free trial • No credit card required

Key Takeaways

  • 💡 Stripe's default retry logic recovers only 30% of failed payments
  • 💡 Decline codes tell you WHY a payment failed — use them to build smarter retries
  • 💡 Expired cards should never be retried — send dunning emails immediately
  • 💡 Insufficient funds needs 3-7 day delays to align with payday
  • 💡 Custom retry logic via webhooks can help achieve the industry benchmark of 85-94% recovery
  • 💡 Enable Card Account Updater and Network Tokens to prevent failures
  • 💡 Track recovery rate by decline code to optimize your strategy

The bottom line: Stripe's default retries are a good starting point, but not optimized for maximum recovery. By understanding decline codes and implementing smart retry logic, you can recover 3x more failed payments (studies show 85-94% is achievable) and save thousands in MRR every month.

About Revive: We build decline code-aware retry logic and automated dunning for Stripe — designed to help achieve industry-leading recovery rates without writing any code. Connect your Stripe account in one click and start recovering revenue today.