Stripe Failure Codes: The Complete Guide
Stripe payment failures each have a reason code. Learn what card_expired, insufficient_funds, do_not_honor, and generic_decline mean, plus the email and retry timing to use next.
What each Stripe failure code means and 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.
That is the expensive mistake. A failed subscription payment is not one problem. It can mean the card expired, the customer has a temporary cash issue, the bank blocked the charge, the issuer needs authentication, or the payment network had a bad moment.
Stripe gives you the first clue in the failure code. Your recovery workflow should use it.
This guide covers the Stripe failure codes SaaS teams are most likely to see, what each code means, when to retry, what email to send, and when a founder should step in manually.
If you want to compare your own numbers before fixing the workflow, use the free Stripe failed payment benchmark. For the full recovery system around failed invoices, read the Stripe failed payment recovery software guide. If you already know the failure reason and need a cadence, use the Stripe dunning schedule calculator.
Quick answers for common Stripe failure codes
| Stripe code | What it usually means | What to do next | Customer-safe message |
|---|---|---|---|
expired_card / card_expired | The saved card is out of date. | Send an update-card link immediately. | "Your card on file appears to be expired." |
insufficient_funds | The card did not have enough available balance or limit at that moment. | Wait, retry later, and keep an optional update link available. | "We will retry your payment in a few days." |
do_not_honor | The issuing bank blocked the charge without giving a public reason. | Explain the bank block and offer bank approval or another card. | "Your bank did not approve this subscription charge." |
generic_decline | The bank declined the payment without a useful customer-safe reason. | Use conservative copy and offer another card or bank approval. | "We could not process your payment." |
card_velocity_exceeded | The card hit a velocity, amount, or limit rule. | Check Stripe's advice code before retrying; otherwise ask for issuer approval or another payment method. | "We couldn't process this payment. Please try another payment method or contact your card issuer." |
transaction_not_allowed | The card or bank settings do not allow this type of transaction. | Ask the customer to contact the bank or use a different payment method. | "Your bank did not allow this subscription charge." |
authentication_required | The customer needs to approve the payment with the bank. | Send an authentication link, not just a generic billing link. | "Your bank needs one extra confirmation." |
What does do_not_honor mean in Stripe?
do_not_honor means the customer's bank declined the charge without giving Stripe a clear public reason. Do not blame Stripe or the customer. Tell the customer the bank did not approve the subscription charge, then offer two paths: approve it with the bank or add another card.
What does transaction_not_allowed mean in Stripe?
transaction_not_allowed means the card or bank settings do not permit the attempted charge. This can happen with corporate cards, debit cards, cross-border transactions, card controls, or subscription restrictions. Ask the customer to contact the bank or use another payment method.
What does generic_decline mean in Stripe?
generic_decline means the payment failed but the issuer did not provide a precise reason you can safely pass to the customer. Keep the email short, offer a secure billing link, and avoid inventing a diagnosis.
How Stripe failure codes work
When a charge fails, Stripe can return two useful pieces of data:
decline_code- the specific reason the card network or bank declined the charge, such asexpired_card,insufficient_funds, ordo_not_honor.failure_code- a broader failure category on the charge or PaymentIntent object.
For payment recovery, decline_code is usually the most valuable signal. It tells you whether the customer needs to update a card, wait for a retry, call the bank, authenticate a payment, or use a different payment method.
You can find the signal in two places:
Via webhook
Listen for invoice.payment_failed events. The invoice's payment error can include the failure code and 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, open the failed charge, and look for the failure reason in the charge detail view.
Why Stripe payments fail
Most failed Stripe payments fall into five practical groups.
| Failure group | Common Stripe codes | What it means | Recovery posture |
|---|---|---|---|
| Expired or replaced card | expired_card, often searched as card_expired | The customer has a new card but Stripe still has the old one. | Send an immediate update-link email. |
| Temporary funds or limit issue | insufficient_funds, card_velocity_exceeded | The card may work later, but not right now. | Wait, retry carefully, and avoid shaming copy. |
| Bank block | do_not_honor, restricted_card | The issuer blocked the transaction without giving you a clean reason. | Explain the bank block and offer two actions. |
| Generic decline | generic_decline, card_declined | The bank declined the payment but did not share enough detail. | Use conservative copy and offer another card. |
| Customer action required | authentication_required, incorrect_cvc, incorrect_zip, transaction_not_allowed | The customer needs to confirm, correct, approve, or change something. | Send the right secure action link or payment-method path. |
The point is not to show raw codes to customers. The point is to translate each code into the safest next action.
The 4 most common Stripe failure codes in SaaS
These four buckets cover the highest-leverage recovery work for most small SaaS teams. The exact mix varies by geography, price point, card type, and customer segment, but the email logic is stable.
expired_card or card_expired - the easy card update
Stripe commonly documents this as expired_card, while founders often search for card_expired. For recovery purposes, treat them as the same branch.
What it means
The saved card is out of date. The card number may still belong to the customer, but the expiration date has passed. The customer probably has a replacement card already.
What the customer experiences
Usually nothing. They may have updated the card for personal services and forgotten your SaaS still has the old one saved. They are not necessarily unhappy, and they are not necessarily trying to cancel.
Email recommended
Use a friendly, specific card-update email. The tone should be calm, not urgent.
Subject:
Your card on file expired
Body:
Hi [Name],
Your payment for [Product] did not go through because the card on file appears to be expired.
You can update it here in under a minute: [secure billing link].
After that, we will retry the invoice automatically.
Timing
Send this on day 0, as soon as the payment fails. Do not wait several days to notify the customer. The fix is obvious and the tone can be light.
What not to do
Do not make it sound like the customer did something wrong. Do not use aggressive suspension language in the first email. Do not keep retrying the same expired card every few hours.
Stripe Smart Retries fit
Low. The underlying issue does not resolve itself, so retries will usually keep failing until the customer updates the card.
insufficient_funds - the timing problem
What it means
The issuer declined the payment because the customer did not have enough available funds, credit, or card limit at that moment.
This does not always mean the customer is in financial trouble. It can be a temporary balance issue, a debit card waiting for payroll, a corporate card limit, or a bank rule that resets later.
What the customer experiences
The customer may know exactly what happened. A heavy-handed message can feel embarrassing. The recovery path is less about "fix your card" and more about "we will try again later."
Email recommended
Use an empathetic wait-and-retry email. Keep the update link available, but make it optional.
Subject:
We will retry your payment in a few days
Body:
Hi [Name],
Your payment for [Product] did not go through today.
No action is needed right now - we will retry it automatically in a few days.
If you would rather use another card, you can update your payment method here: [secure billing link].
Timing
Wait before escalating. For many SaaS teams, day 3 and day 7 retries perform better than repeated immediate retries. If the account is high-value, add a short human note before the last attempt.
What not to do
Do not retry instantly in a tight loop. Do not lead with "update your card" unless you see repeated failures or the customer asks for another method. Do not spell out "insufficient funds" in customer copy.
Stripe Smart Retries fit
High. This is one of the cases where a delayed retry can work well because the card may succeed later without any customer action.
do_not_honor - the bank block
What it means
The issuing bank blocked the transaction and did not share a public reason. It can be fraud prevention, risk scoring, geography, merchant category, card settings, or another bank-side rule.
What the customer experiences
This is confusing. The card may work elsewhere five minutes later. From the customer's perspective, nothing is wrong with the card. From your perspective, Stripe says the bank blocked this charge.
That means the customer needs two clear paths: approve the charge with the bank, or use a different payment method.
Email recommended
Explain the bank block without sounding accusatory.
Subject:
Your bank blocked this subscription charge
Body:
Hi [Name],
Your bank did not approve the latest [Product] payment. This can happen even when the card works elsewhere.
You can either approve the charge with your bank, or add another card here: [secure billing link].
Once that is done, we will retry the invoice.
Timing
Send on day 0. Another silent retry often repeats the same decline, so give the customer context before the next attempt.
What not to do
Do not say "Stripe blocked your card" if the issuer was the blocker. Do not imply the customer entered bad information. Do not send only a generic card-update email.
Stripe Smart Retries fit
Low to moderate. A later retry may succeed if the bank's risk filter clears, but the customer often needs to authorize the charge or use another card.
generic_decline or card_declined - the unclear decline
What it means
The card was declined, but the failure does not give a precise customer-safe reason. Stripe may show generic_decline, a card_declined error type, or a failure state where the useful detail is limited.
What the customer experiences
The customer gets little context. They may assume your product is broken, their bank is broken, or they did something wrong. Your email should reduce uncertainty without pretending to know the exact cause.
Email recommended
Use conservative dual-option copy.
Subject:
We could not process your payment
Body:
Hi [Name],
The bank did not approve the latest [Product] payment.
You can try another card here: [secure billing link], or approve the charge with your bank and we will retry the invoice.
Timing
Send after one clean delayed retry window, unless Stripe advice says customer action is needed immediately. If the same code repeats, switch from automated retry to customer action.
What not to do
Do not over-explain. Do not diagnose the exact issue when Stripe did not give you one. Do not keep sending the same generic "payment failed" email every day.
Less frequent codes that still matter
The long tail of Stripe decline codes can be small in volume but painful when handled incorrectly.
card_velocity_exceeded
This code usually signals an issuer limit rather than a broken subscription. Check Stripe's advice code before another attempt; otherwise give the customer an issuer-approval or alternate-payment path. Read the Stripe card_velocity_exceeded recovery guide for the full customer message, timing, and escalation workflow.
transaction_not_allowed
The bank or card settings do not allow this type of transaction. This can show up when a card blocks subscriptions, online payments, international charges, business spend categories, or merchant categories.
Recommended copy:
Your bank did not allow this subscription charge. You can approve it with the bank or use another payment method here: [secure billing link].
Recommended timing: do not keep retrying the same card without customer action. Send the explanation on day 0, then retry after the customer confirms the bank setting changed or adds a new card.
lost_card and stolen_card
Do not send an email that literally says "your card was stolen." Sensitive decline codes should usually be translated into generic customer-safe copy.
Recommended copy:
Your bank did not approve this card. Please add a different payment method to keep your subscription active.
Recommended timing: do not retry the same card. Ask for a new payment method.
fraudulent
This usually deserves caution. Do not retry aggressively. Send one neutral email asking for a different payment method, then route repeated cases to manual review.
authentication_required
The card may be fine, but the issuer requires a confirmation step such as 3D Secure or Strong Customer Authentication. A normal card-update email is not enough.
Recommended copy:
Your bank needs one extra confirmation before we can process this subscription payment. Use this secure link to approve it: [authentication link].
Recommended timing: do not retry off-session until the customer completes authentication.
incorrect_cvc, incorrect_zip, and incorrect_number
These point to wrong card details. Do not retry the same details. Ask the customer to re-enter payment information through a secure link.
Recommended copy:
The bank could not verify part of your card information. Please re-enter your payment details here: [secure billing link].
issuer_not_available and processing_error
These are often temporary processor, network, or issuer issues. In many cases, the best first step is a quiet retry later, not an immediate customer email.
Recommended copy, if an email is needed:
The payment network could not complete the charge. We will retry automatically. No action is needed right now.
currency_not_supported
The card does not support the currency you are charging in. Ask the customer to use a card that supports your billing currency, and include the currency in the email.
Summary table
| Code | Timing | Retry posture | |
|---|---|---|---|
expired_card / card_expired | Friendly update link | Day 0 | Retry after update |
insufficient_funds | Empathetic wait-and-retry | Day 3 | Delayed retry |
do_not_honor | Bank explanation | Day 0 | Retry after customer action |
generic_decline / card_declined | Conservative dual-option email | Day 1 | Delayed retry, then action |
card_velocity_exceeded | Generic decline message | Advice-code dependent | Smart Retry or customer action |
transaction_not_allowed | Bank/card setting explanation | Same day | Retry after customer action |
lost_card / stolen_card | Generic new-card request | Same day | Do not retry same card |
fraudulent | Neutral new-card request | Same day | Manual review if repeated |
authentication_required | Authentication link | Immediately | Wait for authentication |
incorrect_cvc / incorrect_zip | Re-enter card details | Same day | Retry after correction |
issuer_not_available / processing_error | Usually no email first | Retry later | Quiet retry first |
currency_not_supported | Explain currency mismatch | Same day | Ask for another card |
How to turn the code into a dunning sequence
A code-aware recovery system does not need hundreds of templates. It needs a few clean branches.
Start with four primary tracks:
- Expired card: send a day 0 update-link email, then retry after update.
- Funds issue: wait, retry gently, and keep the update link optional.
- Bank block: explain the issuer block immediately and offer bank approval or another card.
- Generic decline: use conservative language and move to customer action if it repeats.
- Transaction not allowed: ask for bank approval or a different payment method before another retry.
Then add guardrails:
- Sensitive hard declines should not expose the literal failure reason.
- Authentication failures should route to an authentication flow, not a generic billing link.
- Temporary network failures should get quiet retries before you bother the customer.
- High-value accounts should trigger a founder or support escalation before the last retry.
This is where many SaaS teams recover more revenue without sending more email. They send fewer generic messages and more precise ones.
Example recovery sequence by code
For expired_card:
- Day 0: friendly update-link email.
- Day 2: reminder with the same link.
- Day 5: final notice before access is paused.
For insufficient_funds:
- Day 0: light internal note or quiet retry planning.
- Day 3: retry automatically.
- Day 4: optional update-link email if the retry failed.
- Day 7: final retry and human note for high-value accounts.
For do_not_honor:
- Day 0: explain that the bank blocked the charge.
- Day 1: retry only if the customer acted or Stripe advice allows it.
- Day 3: ask them to call the bank or use another card.
- Day 7: founder note for accounts worth saving manually.
For generic_decline:
- Day 1: conservative dual-option email.
- Day 3: delayed retry.
- Day 5: ask for another payment method.
- Day 7: final reminder before suspension.
These are starting points. Your price point, customer type, geography, and invoice amount will change the best cadence. But the principle stays the same: the failure code chooses the first message and the retry posture.
If you want a starting cadence for your own invoice value and account type, use the free Stripe dunning schedule calculator.
Build vs buy
Build it yourself
Listen for invoice.payment_failed webhooks, parse the payment error, map failure codes to recovery branches, generate secure billing links, and trigger the right email through your provider.
That is viable if you have engineering time and only need basic logic.
Use a dedicated tool
Dunlo reads the failure code from Stripe, routes to the right email sequence, handles timing, tracks recovered revenue, and flags high-value accounts for founder review. It is free during beta.
FAQ
Is card_expired the same as expired_card?
For recovery purposes, yes. Stripe commonly documents expired_card, while many founders search for card_expired. Treat both as the expired-card recovery branch.
Should I mention Stripe failure codes to customers?
Usually no. Codes are useful internally, but customers need plain language. Say "your card appears to be expired," "your bank did not approve this charge," or "we will retry in a few days."
Should I retry every failed payment immediately?
No. Immediate retries can work for temporary processor failures, but they are usually wrong for insufficient_funds, hard declines, authentication failures, and bank blocks. Let the code guide the retry timing.
What if Stripe gives me an advice code too?
Use it. Stripe advice codes can give more specific next-action guidance. The failure code explains what happened; advice can help decide whether to retry, wait, or request customer action.
The bottom line
Stripe failure codes are not just technical labels. They are recovery instructions.
expired_card needs a calm update link. insufficient_funds needs empathy and time. do_not_honor needs bank-context copy before another retry. Generic declines need conservative language and clear options. Sensitive codes need customer-safe wording. Authentication codes need an authentication flow.
This is exactly what Dunlo automates: it reads the failure code first, then sends the right email at the right time. If you want the benchmark and recovery logic handled for you, connect Stripe to Dunlo. It is free during beta.