~62%
of payment failures come from just 3 decline codes
25–30%
of declines are expired cards — fully preventable
40–60%
recovery rate with a proper dunning sequence
72h
optimal retry window for most soft declines
What is a Stripe decline code?
When a card payment fails, Stripe receives a response from the customer's issuing bank. That response contains two pieces of information: an error code (the category of the failure) and a decline code (the specific reason). The decline code lives in the last_payment_error.decline_code field of a PaymentIntent or in charge.failure_code on a Charge object.
In a subscription context, Stripe surfaces the decline code on the invoice.payment_failed webhook event. This is the event your dunning system (or MRRescue) listens to — and the decline code it contains determines the entire recovery strategy.
From the invoice.payment_failed webhook
{
"type": "invoice.payment_failed",
"data": {
"object": {
"last_payment_error": {
"code": "card_declined",
"decline_code": "insufficient_funds", ← this is what matters
"message": "Your card has insufficient funds."
}
}
}
}Most teams log the error and send the same generic "your payment failed" email regardless of which code fired. That's a mistake — because the right action after insufficient_funds is completely different from the right action after expired_card.
Soft declines vs. hard declines
Before diving into individual codes, understand the fundamental split: soft declines are temporary failures that can resolve on their own — the customer's situation changes, or the bank's rules relax. Hard declines are permanent: the card is invalid, reported stolen, or blocked indefinitely. Retrying a hard decline is wasteful at best and damaging to your Stripe account health at worst.
Soft decline
Temporary failure. The card is valid but the transaction couldn't be processed right now. Retry after an appropriate waiting period.
insufficient_fundsdo_not_honorgeneric_declinetry_again_laterHard decline
Permanent failure. The card is invalid, stolen, expired, or blocked. Retrying will always fail and can trigger Stripe account flags.
expired_cardlost_cardstolen_cardfraudulentThe 6 most common decline codes
These six codes account for roughly 90% of all subscription payment failures on Stripe. For each one: what it means, whether to retry, and what to write in your recovery email.
insufficient_fundsSoft~30% of failuresWhat it means
The customer's card or bank account doesn't have enough funds to cover the charge at this moment.
Retry strategy
Yes — retry in 3–5 days (payday cycle). Do not retry immediately.
Email copy approach
Keep it casual and blame-free. Say something like "We tried to process your renewal but the payment didn't go through — this happens sometimes. Your access is safe for the next 3 days, and we've set a reminder to retry." Include a direct link to update their card as a secondary option.
do_not_honorSoft/Hard~18% of failuresWhat it means
The issuing bank declined without a specific reason. Usually a temporary bank-side rule — fraud prevention, international block, or card spending limit reached.
Retry strategy
Retry once after 48–72 hours. If it fails again, treat as hard decline.
Email copy approach
Ask the customer to call their bank to approve the charge, or to try a different card. Be specific: "Your bank declined the payment — this is usually a temporary block. Please call the number on the back of your card and ask them to approve future charges from [your company name]."
card_declinedSoft~14% of failuresWhat it means
Generic decline from the issuer — the bank didn't provide a more specific reason. Similar to generic_decline.
Retry strategy
Retry once after 24–48 hours.
Email copy approach
Same approach as do_not_honor — ask for a card update or to contact their bank. Keep the tone light and solution-focused.
expired_cardHard (but recoverable)~25–30% of failuresWhat it means
The card's expiration date has passed. The card is permanently invalid — retrying will always fail.
Retry strategy
Never retry the same card. Ask for a new payment method immediately.
Email copy approach
This is the easiest recovery — the customer just needs to update their card. Be direct: "Your card ending in XXXX expired in [month/year]. Add your new card in 30 seconds and your subscription will continue without interruption." Link directly to the payment update page.
generic_declineSoft~8% of failuresWhat it means
The issuer declined for an unspecified reason. Often a fraud prevention rule, spending limit, or international block that the bank doesn't disclose.
Retry strategy
Retry once after 24–48 hours.
Email copy approach
Same as do_not_honor — bank contact + card update option.
fraudulentHard — do not retry~5% of failuresWhat it means
The card has been flagged for suspected fraud — either by the bank, by Stripe Radar, or because it was reported stolen. This is a hard block.
Retry strategy
Never retry. Retrying flagged cards can trigger Stripe account review.
Email copy approach
Send a gentle suspension notice. Ask the customer to reach out and add a new payment method. Do not reference fraud explicitly — say "We had a problem processing your card" and offer a direct line to your support team.
Full decline code reference table
Quick-reference for every Stripe decline code your subscription business is likely to see. Bookmark this page or copy it into your internal runbook.
| Code | Type | Retry? | Action |
|---|---|---|---|
insufficient_funds | Soft | ✓ Yes | Retry in 3–5 days |
do_not_honor | Soft | ✓ Yes | Retry once after 48–72h |
card_declined | Soft | ✓ Yes | Retry once after 24–48h |
expired_card | Hard | ✗ No | Request new card immediately |
generic_decline | Soft | ✓ Yes | Retry once after 24–48h |
fraudulent | Hard | ✗ No | Suspend + request new card |
pickup_card | Hard | ✗ No | Suspend immediately |
stolen_card | Hard | ✗ No | Suspend immediately |
lost_card | Hard | ✗ No | Suspend immediately |
card_velocity_exceeded | Soft | ✓ Yes | Retry after 24–48h |
currency_not_supported | Hard | ✗ No | Contact customer |
not_permitted | Hard | ✗ No | Request new card |
transaction_not_allowed | Soft | ✓ Yes | Retry after 24h |
try_again_later | Soft | ✓ Yes | Retry after 1–4h |
withdrawal_count_limit_exceeded | Soft | ✓ Yes | Retry next billing cycle |
processing_error | Soft | ✓ Yes | Retry after 1–4h |
call_issuer | Soft | ✓ Yes | Ask customer to call bank, then retry |
card_not_supported | Hard | ✗ No | Request different card type |
duplicate_transaction | Soft | ✗ No | Check for duplicate charges |
incorrect_number | Hard | ✗ No | Request new card details |
incorrect_cvc | Soft | ✗ No | Ask customer to update card |
invalid_expiry_month | Hard | ✗ No | Ask customer to update card |
invalid_expiry_year | Hard | ✗ No | Ask customer to update card |
Retry strategy by decline type
The optimal retry timing isn't arbitrary — it's based on why the card failed. Stripe's Smart Retries algorithm accounts for some of this, but it treats all soft declines similarly. Pairing Smart Retries with decline-code-aware logic significantly improves recovery rates.
Insufficient funds
insufficient_fundswithdrawal_count_limit_exceededWait 3–5 days. Many customers get paid on the 1st or 15th of the month — retry around those dates. If the initial failure was mid-month, a retry at month-end often succeeds. Limit to 3 retries total.
Issuer declined (no specific reason)
do_not_honorgeneric_declinecard_declinedRetry once after 48–72 hours. If the second attempt fails, stop automatic retries and send an email asking for a new card or a bank call. More than 2 retries on these codes risks increasing your Stripe decline rate.
Temporary network / processing error
processing_errortry_again_laterRetry after 1–4 hours. These are often infrastructure glitches on the network or bank side, not customer issues. A quick retry usually succeeds.
Expired card
expired_cardinvalid_expiry_monthinvalid_expiry_yearDo not retry. The card is dead. Send an update-card email immediately with a direct link. If the customer hasn't updated after 7 days, send a final warning before suspending.
Fraud / stolen / lost
fraudulentstolen_cardlost_cardpickup_cardNever retry. Suspend the subscription and send a support-focused email. Flag the customer for manual review if you're on the Pro tier.
What to say in your recovery email
Most failed payment emails say the same thing regardless of why the card declined. That's a missed opportunity. Customers respond better when the email addresses their actual situation — especially for declines they can act on immediately.
insufficient_fundsSubject
We'll try again in a few days — no action needed
Body (edit to match your tone)
Hi [Name], we tried to process your renewal but your card had insufficient funds. No worries — we've set a reminder to retry in 3 days. If you'd like to use a different card in the meantime, you can update it here: [link]. Your access continues as normal.
Why this works: Blame-free. Gives the customer time and an easy out. The 'no action needed' subject kills anxiety.
expired_cardSubject
Your card expired — update it in 30 seconds
Body (edit to match your tone)
Hi [Name], your card ending in XXXX expired in [month/year]. Add your new card here: [link]. It takes under a minute, and your subscription will continue without interruption.
Why this works: Direct and specific. The customer knows exactly what's wrong and how to fix it. No ambiguity.
do_not_honor / generic_declineSubject
Your bank declined — a quick fix
Body (edit to match your tone)
Hi [Name], your bank declined our payment request — this is usually a temporary block, not a problem with your card. The fastest fix: call the number on the back of your card and ask them to approve future charges from [company name]. Alternatively, add a backup card here: [link].
Why this works: Gives a clear next action (bank call) that actually works for these declines.
For the full 6-email sequence with decline-code-aware copy for each step, see our failed payment email templates guide.
How to automate decline-code-aware recovery
Handling decline codes correctly at scale requires automation. There are two approaches:
Build it yourself: Listen to the invoice.payment_failed webhook. Read the decline_code. Build routing logic (if/else trees) that sends a different email and schedules a different retry for each category. Implement retry scheduling, sequence stopping, idempotency, and unsubscribe handling. Budget 3–6 weeks of engineering time and ongoing maintenance.
Use a dedicated tool: MRRescue's failed payment recovery reads the decline code from every invoice.payment_failed event and automatically sends the right recovery email with the right messaging. No code required — connects to your Stripe account via OAuth in 2 minutes.
Frequently asked questions
What is a Stripe decline code?
A Stripe decline code is a machine-readable string returned by the card issuer when a payment is refused. It lives in the last_payment_error.decline_code field of a PaymentIntent. It tells you exactly why the bank said no — which determines the right retry strategy and recovery email.
What is the most common Stripe decline code?
For subscription businesses, the most common are insufficient_funds (~30%), do_not_honor (~18%), and expired_card (~25–30%). Together they account for roughly 60–70% of all subscription payment failures.
Should I retry a payment after a do_not_honor decline?
Yes, but only once and after 48–72 hours. do_not_honor is often a temporary bank decision. More than two retries on this code can escalate to a hard block or trigger Stripe account flags.
What does generic_decline mean in Stripe?
generic_decline is a catch-all for declines the bank won't explain — usually a fraud prevention rule or spending limit. Treat it like do_not_honor: retry once after 24–48 hours and send an email asking the customer to contact their bank.
Which Stripe decline codes should never be retried?
Hard codes that should never be retried: fraudulent, stolen_card, lost_card, and pickup_card. Retrying these risks Stripe account flags. Immediately ask the customer to add a new payment method.