← Back to Blog

2026-03-09 · RetryKit Team

Soft vs Hard Decline in Stripe: What They Mean and How to Recover Each

Learn the difference between soft and hard declines in Stripe, why each happens, and the proven recovery strategies that save SaaS revenue for both decline types.

Soft vs Hard Decline in Stripe: What They Mean and How to Recover Each

Not all failed payments are the same problem. A payment_intent.payment_failed event in your Stripe dashboard can mean a dozen different things — and the recovery strategy that works for one will actively waste time on another.

The most important split: soft declines versus hard declines. This single distinction determines whether you should retry automatically, email the customer immediately, or both. If you're running the same recovery playbook for every decline type, you're under-recovering the ones that should resolve and burning retry attempts on the ones that won't.

Soft Declines: Temporary, Retriable

A soft decline is a temporary failure. The card issuer rejected the transaction, but the underlying payment method is still valid. The charge could succeed if retried at a different time.

Common Stripe decline codes that are soft:

  • insufficient_funds — customer's balance was low at billing time; might clear by Friday if they get paid weekly
  • processing_error — bank or network hiccup; retrying within hours usually works
  • issuer_not_available — the card network was temporarily unavailable
  • generic_decline (sometimes) — catch-all that can be temporary

The key is temporary. The card hasn't been canceled. The account isn't closed. Something transient blocked the transaction in that moment.

insufficient_funds is the most common soft decline we see across RetryKit's connected accounts. And it's genuinely recoverable — we've seen it clear on the 4th attempt 5 days after the initial failure. A $5.99 invoice, written off as unrecoverable by a naive system, goes through when you retry at the right time.

Common causes of soft declines

Insufficient balance is the obvious one, but there are others. Velocity checks — some issuers flag multiple charges in a short window as suspicious and temporarily block the card. Issuer processing errors where the bank's systems hit a glitch. Over-limit situations where the customer is near but hasn't technically exceeded their credit ceiling.

In all these cases: the card is fine, the customer is fine, and the right retry timing can recover the payment without any customer interaction at all.

Hard Declines: Permanent, Requires Action

A hard decline is a permanent failure. The card issuer has definitively rejected the transaction, and retrying the same card will not succeed.

Common hard decline codes in Stripe:

  • expired_card — the card is past its expiration date
  • stolen_card, lost_card — the card no longer exists
  • pickup_card — the issuer wants the card physically seized (something's seriously wrong)
  • do_not_honor (when persistent) — the bank has a standing block

Hard declines mean the payment method itself is broken. No amount of retrying will fix an expired or canceled card — and retrying hard declines can actually hurt you by flagging your merchant account for excessive failed attempts.

Expired cards are the most common source of hard declines in subscription businesses. Cards expire silently. Customers don't always notice until their access gets cut off.

Why This Distinction Changes Everything About Recovery

The numbers make the case clearly:

| Decline Type | Baseline Recovery Rate | Optimized Recovery Rate | |---|---|---| | Soft decline | 30-40% | 60-75% | | Hard decline | 5-10% | 15-25% |

If you're running the same three-retry schedule for both, you're leaving soft decline recovery on the table (they need different timing, not just more retries) and wasting attempts on hard declines that won't respond to retries at all.

The 15-25% "optimized" hard decline recovery comes entirely from getting the customer to update their payment method quickly — not from retrying.

Recovering Soft Declines: Timing is the Lever

Soft declines respond well to intelligent retry timing. The goal is to retry when the underlying issue has likely resolved.

Retry on likely payday windows. insufficient_funds declines often clear when customers get paid. For US customers, the 1st and 15th are common payroll dates; Friday afternoons bring direct deposits. Timing retries around these windows has shown 20-30% lift in recovery rates in well-run SaaS billing setups.

Space out retries with exponential backoff. Stripe's default schedule isn't terrible, but it's not optimized for payment cycles. A schedule like: 24 hours → 3 days → 5 days → 7 days gives the most coverage across different customer types. Our retry schedule at RetryKit runs exactly this — 1 day, 3 days, 5 days, 7 days, 14 days.

Don't immediately notify the customer. For soft declines, the best-case scenario is the retry succeeds before the customer sees any email. Fire a dunning email too early and you create confusion for a problem that was about to self-resolve. We send dunning emails at retry 2 and retry 4 — after the automatic system has already tried and genuinely needs customer involvement.

Time-of-day can matter. Some issuers are more conservative with fraud checks during off-hours. Avoiding 2-4 AM retries in the customer's timezone is a minor optimization, but it stacks with everything else.

Recovering Hard Declines: Speed is the Lever

For hard declines, every day of delay increases churn probability. The customer needs to update their payment method — and the longer you wait to tell them clearly, the less likely they are to do it.

Notify immediately with a direct update link. Don't wait for your retry schedule to exhaust itself before reaching out. The moment you confirm a hard decline (expired card, stolen card, lost card), send an email with a direct link to update payment. Don't make them log in, navigate to settings, find billing. One click from email to payment update page.

Use in-app banners for active users. Email open rates are ~20-30%. If the customer uses your product daily, an in-app banner will reach them far faster than email. Show a non-dismissable banner when there's an active hard decline on the account.

Leverage Stripe's Card Account Updater. Stripe automatically updates stored card details when customers receive new cards from their issuer. For expired cards specifically, there's often a 48-hour window after the failure where Account Updater propagates new card details — worth a single retry at 48 hours before fully pivoting to customer notification, just to capture cards that updated automatically.

Give a grace period. Don't cancel on day 2. Most customers with hard declines had no idea their card expired. A 7-14 day window gives them time to update before losing access. Reactivation rates after a full cancellation are 30-40% lower than recovery rates from past_due status — the billing relationship still exists, the card is still on file, preserve that.

Building the Routing System

The practical implementation at the webhook level:

  1. invoice.payment_failed fires → fetch the charge object immediately
  2. Read outcome.decline_code → classify as soft, hard, or ambiguous
  3. Route to the right playbook:
    • Soft → queue retry at 24h, no email; escalate to dunning email after retry 2
    • Hard → email immediately, skip retries (except single 48h retry for expired_card to catch Account Updater)
    • Fraud → flag for review, no retry, personal outreach for high-value customers
  4. Track by decline type — measure recovery rates separately for each bucket

Step 4 is the one most teams skip. If you're measuring "overall recovery rate" without segmenting by decline type, you can't know whether your soft decline recovery is actually optimized or whether you're accidentally recovering hard declines that were about to self-resolve anyway.

Stop Running One Playbook for Everything

The difference between a 30% and a 65% recovery rate often comes down to this one distinction. Soft declines need patience and smart timing. Hard declines need fast, clear customer communication.

Building this routing system from scratch — parsing Stripe webhooks, classifying decline codes, managing retry queues, coordinating email timing with retry state — takes several weeks of engineering work. That's the problem RetryKit solves: automatic decline classification, optimized retry logic for soft declines, immediate card update flows for hard declines, all wired up to your Stripe account without custom code.

Whether you build it yourself or use a tool, stop treating every failed payment the same way. The revenue you're leaving on the table is already paid for by customers who want to keep using your product.

Ready to recover lost revenue?

Connect your Stripe account in under 2 minutes. Pay only on recovered revenue.

Try RetryKit Free