dunlo
Back to blog
stripefailure codessaas

Stripe failure codes — the complete guide

3 min read

Stripe Failure Codes: What Each One Means and Exactly What to Do About It

When a Stripe payment fails, most SaaS founders send the same email to everyone.

"Your payment failed. Please update your billing information."

It's the wrong move — and it's costing you recoverable revenue every month.

Every failed payment in Stripe comes with a reason code. That code tells you exactly what went wrong: whether the card expired, whether the bank blocked the charge, whether it was a temporary funds issue. Each situation calls for a completely different response, different timing, and different tone.

This guide covers every Stripe failure code you're likely to encounter, what it means, and precisely what to do — so you can stop treating all failed payments the same way.


How Stripe Failure Codes Work

When a charge fails, Stripe returns two pieces of data:

  • decline_code — the specific reason the card network or bank declined the charge (e.g., card_expired, insufficient_funds, do_not_honor)
  • failure_code — a broader category code on the charge or PaymentIntent object

For dunning purposes, decline_code is what matters. It's the most granular signal you have about what actually went wrong.

You can access it in two ways:

Via webhook (recommended): Listen for invoice.payment_failed events. The charge object inside the event contains failure_code and last_payment_error.decline_code.

{
  "type": "invoice.payment_failed",
  "data": {
    "object": {
      "last_payment_error": {
        "code": "card_declined",
        "decline_code": "insufficient_funds",
        "message": "Your card has insufficient funds."
      }
    }
  }
}

Via Stripe Dashboard: Go to Payments → find the failed charge → look under "Failure reason" in the charge detail view.


The 4 Failure Codes You'll See Most Often

These four codes account for the vast majority of failed payments in SaaS billing. Get these right and you'll recover most of your involuntary churn.


1. card_expired

What it means: The expiration date on the card has passed. The card number is still valid, the customer still has the account — they just haven't updated their card details with you.

How common: Very common. Cards typically expire every 2–3 years, and customers rarely update their details proactively. Expiry-related failures tend to cluster around January (post-holiday card refreshes) and throughout the year as individual cards hit their expiry dates.

What the customer needs to do: Update to a new card. That's it. They almost certainly already have a replacement card from their bank.

The right recovery approach:

  • Timing: Send immediately — within an hour of the failure. The customer is likely at their desk, and this is an easy fix. Don't wait.
  • Tone: Friendly, zero pressure. Frame it as a quick admin task, not a billing crisis. "Happens to everyone" goes a long way here.
  • Action: One clear call to action — a direct link to update their payment method. Don't ask them to log in, navigate to billing, and find the update button. Give them a one-click link.
  • Subject line example: Quick note about your [Product] subscription, [Name]

Email template:

Hey [Name],

The card we have on file for your [Product] subscription expired at the end of [month] — totally normal, happens to everyone.

Here's a one-click link to update it: [Update payment method →]

Takes about 30 seconds. Once updated, your subscription continues without any interruption.

Let me know if you run into any issues.

[Your name]

What not to do: Don't send an urgent, high-pressure email. The customer didn't do anything wrong — their card just expired. Treat it as a helpful nudge, not a warning.

Stripe Smart Retries effectiveness: Low. The underlying issue (expiry) doesn't resolve itself, so retries will keep failing until the card is updated. Email outreach is essential here.


2. insufficient_funds

What it means: The customer's account didn't have enough balance to cover the charge at the time of the attempt. The card itself works fine — it was just a bad moment to charge it.

How common: Very common, especially for consumer-facing and lower-price-point SaaS. Also spikes at end-of-month when billing cycles from other services have already hit.

What the customer needs to do: Usually nothing. Wait for their account to replenish. The card will work again in a few days.

The right recovery approach:

  • Timing: Do NOT send an email immediately. Wait 3 days before reaching out. Emailing someone the same day their card declined for insufficient funds piles on at the worst possible moment and can damage the relationship.
  • Tone: Calm, non-judgmental, no urgency. They know what happened. They don't need it spelled out.
  • Action: Let them know there was an issue with their payment, without making them feel bad about it. Offer a retry or a payment link — but don't make the email feel like a collections notice.
  • Subject line example: A quick note about your [Product] subscription

Email template:

Hey [Name],

Just a heads up — we had a small issue processing your [Product] subscription payment recently.

It's likely just a timing thing. If you'd like to retry the payment now, you can do that here: [Retry payment →]

Otherwise, we'll try again automatically in a few days. No action needed if you'd prefer to wait.

[Your name]

What not to do: Don't send an urgent email the same day. Don't tell them their card "was declined for insufficient funds" — they know, and spelling it out is embarrassing. Don't suggest they update their payment method (their card works fine).

Stripe Smart Retries effectiveness: High. This is exactly the scenario Smart Retries is designed for — Stripe will wait and retry when the probability of success is higher. Enable Smart Retries and let it do its job before escalating to email.


3. do_not_honor

What it means: The customer's bank rejected the transaction without giving a specific reason. This is one of the most frustrating codes for customers because it's vague — their card appears to work, and they may not understand why the charge failed.

Common causes:

  • The bank's fraud detection flagged the transaction (especially common for SaaS subscriptions billed from unfamiliar merchant names or foreign entities)
  • The card has international transaction restrictions
  • The bank has a temporary hold on the card
  • A security freeze the customer may not be aware of

How common: Second most common decline code after insufficient_funds. Particularly frequent for international customers or businesses with generic merchant names on statements.

What the customer needs to do: Either call their bank to authorize the transaction, or use a different card.

The right recovery approach:

  • Timing: Send within a few hours. This is time-sensitive — the longer you wait, the more likely the customer assumes everything is fine or mentally moves on.
  • Tone: Explanatory. The customer is likely confused. Your job is to explain what happened in plain language and give them two clear options.
  • Action: Two paths — call your bank, or use a different card. Don't give them one option; some customers can't or won't call their bank, and giving them an alternative increases recovery significantly.
  • Subject line example: Issue with your [Product] payment — here's what happened

Email template:

Hey [Name],

We had an issue processing your [Product] subscription payment, and I wanted to explain what happened.

Your bank blocked the transaction — this is pretty common with subscription billing and usually isn't caused by anything on your end. Banks occasionally flag recurring charges from unfamiliar merchant names as a precaution.

You have two options to fix it:

Option 1: Call the number on the back of your card and ask them to authorize recurring payments from [Your Company/Merchant Name]. Then click here to retry: [Retry payment →]

Option 2: Update to a different card: [Update payment method →]

Either works. Let me know if you have any questions — happy to help sort this out.

[Your name]

What not to do: Don't just send a generic "update your card" email. The card works — that's not the problem. Customers who try to update to the same card will see it fail again and give up entirely.

Stripe Smart Retries effectiveness: Low to moderate. If the bank's fraud filter clears (which sometimes happens automatically), a retry may succeed. But in most cases, the customer needs to take action before the charge will go through.


4. card_declined (generic)

What it means: The card was declined, but Stripe didn't receive (or can't share) a more specific reason. This is the catch-all decline code — it covers situations where the bank declines without providing detailed information.

Possible causes include: card restrictions, account issues, temporary bank holds, or decline policies that banks don't expose to merchants.

How common: Common, but less informative than the specific codes above. When you see this, you know something went wrong but not exactly what.

What the customer needs to do: Try a different card, or contact their bank to understand why it was declined.

The right recovery approach:

  • Timing: Same day — within a few hours.
  • Tone: Helpful and low-pressure. You don't know exactly what happened, so don't pretend you do.
  • Action: Offer two paths (retry with a different card, or contact support) without creating urgency.
  • Subject line example: We had trouble processing your [Product] payment

Email template:

Hey [Name],

We had trouble processing your [Product] subscription payment. Unfortunately, we don't have more detail on the specific reason — these things happen occasionally.

If you'd like to try a different card, you can update your payment method here: [Update payment method →]

Or if you'd prefer to retry with the same card: [Retry payment →]

Either way, let me know if you need any help sorting this out.

[Your name]

Stripe Smart Retries effectiveness: Variable. Depends on the underlying reason. Smart Retries may succeed if it was a temporary bank issue; it won't succeed if the underlying issue persists.


Less Common But Important Failure Codes

You'll see these less frequently, but knowing how to handle them prevents recoverable payments from slipping through.

card_velocity_exceed

What it means: Too many charges have been attempted on this card in a short time period. Stripe or the card network has throttled the attempts.

What to do: Wait 24 hours before attempting any retry. Do not email the customer urgently — this will resolve itself. A calm notification is appropriate, but the main action is on your side (pause retries).


fraudulent

What it means: The bank has flagged this transaction as potentially fraudulent. In some cases, this may indicate a stolen or compromised card.

What to do: This is one of the few codes where caution is warranted on your end. Don't retry aggressively. Send a single gentle email asking the customer to update their payment method, framed as a security precaution. If you receive multiple fraudulent codes from the same customer account, consider flagging for manual review.


lost_card / stolen_card

What it means: The card has been reported lost or stolen. The bank will not authorize any further charges.

What to do: The customer needs to provide a new card. They may not be aware their card was flagged as lost/stolen (bank fraud systems sometimes act before the customer reports it). Send a polite email asking them to update their payment method, without referencing "lost" or "stolen" — that can create unnecessary alarm.


expired_card

What it means: Functionally identical to card_expired. Handle the same way — immediate email, friendly tone, one-click update link.


processing_error

What it means: A technical error occurred during processing — not a customer issue, not a card issue. Usually transient.

What to do: Retry immediately (or let Smart Retries handle it). If the retry succeeds, no email needed. If it fails again with a different code, treat the new code as the primary issue.


currency_not_supported

What it means: The card doesn't support the currency you're charging in.

What to do: Email the customer explaining that their current card doesn't support the billing currency, and ask them to update to a card that does. Include your billing currency in the email so they know what to look for.


Quick Reference: Failure Code Action Table

Decline codeEmail timingTonePrimary action
card_expiredImmediateFriendly, low pressureUpdate card link
insufficient_fundsD+3Calm, no judgmentRetry link, or wait
do_not_honorWithin hoursExplanatoryCall bank OR new card
card_declinedSame dayHelpfulNew card OR retry
card_velocity_exceedD+1CalmPause retries 24h
fraudulentSame daySecurity-framedUpdate card
lost_card / stolen_cardSame dayNeutralUpdate card (no alarm)
processing_errorOnly if retry failsMatter-of-factRetry first
currency_not_supportedSame dayInformationalCard that supports currency

Implementing Failure-Code-Specific Dunning

The most direct path to implementing this:

Option 1: Build it yourself Listen for invoice.payment_failed webhooks, parse the last_payment_error.decline_code field, and trigger the appropriate email sequence via your email provider (Resend, Postmark, etc.). A day or two of development work.

Option 2: Use a dedicated tool Dunlo handles this automatically — it reads the failure code from Stripe, routes to the right email sequence, handles timing, and flags high-value accounts for personal escalation. Free during beta.

Either way, the principle is the same: the failure code is the most valuable signal you have. Use it.


The Bottom Line

Stripe tells you exactly why every payment failed. Most founders ignore that information and send the same generic email to everyone.

The difference between a 20% recovery rate and a 70% recovery rate isn't marketing sophistication or technical complexity. It's reading the failure code and responding appropriately.

card_expired → immediate, friendly, one-click link. insufficient_funds → wait 3 days, calm email, no urgency. do_not_honor → explain what happened, give two options. card_declined → same day, helpful, low pressure.

That's it. Four different responses to four different problems. Start there, and you'll recover most of what you're currently losing.


Want this handled automatically? Dunlo connects to Stripe, reads every failure code, and sends the right email at the right time — without you having to think about it. Free during beta.

stripefailure codessaas