The 29 decline codes that decide what happens next on a failed subscription payment, sorted into five classes: retry it, ask for a new card, the bank refused without saying why, stop entirely, or unknown. What each one means in plain English, whether to retry, when, and which ones must never get an automated email.
Short answer
Why do Stripe subscription payments fail, and what should you do?
Every failed Stripe charge carries a decline_code, and the 29 codes that matter fall into five classes: retry it (the balance or a limit was short), ask for a new card (the card is unusable), the bank refused without a reason (one retry, then a human), hard stop (never retry, never automate contact), and unknown (claim nothing). The class decides whether to retry, when, what the email says, and whether an email may go out at all.
Stripe's documentation lists decline codes alphabetically with a one-line description each. That is the right reference for a developer handling the invoice.payment_failedevent. It is the wrong shape for the question a finance lead or a CS manager actually has, which is: this customer's payment failed, do we retry it, do we email them, and what do we say?
This page answers that question, code by code, using the classification Exeechain applies to every failed invoice it reads from Stripe. Two things to know before the table. First, the classis what matters; the individual code is only how you find the class. Second, the most important decision is not “retry or not”. It is “is an automated email appropriate at all”, and for eight of the codes the answer is no.
On a failed subscription payment, Stripe fires invoice.payment_failed. The invoice's latest charge (or payment intent) carries failure_code, failure_message, and inside outcome a decline_code such as insufficient_funds. The decline code is the bank's answer; the failure message is Stripe's paraphrase. Use the code. In the Dashboard it is shown on the payment's detail page under the decline reason.
If there is no code, which happens when the failure reached you through an events feed with no invoice behind it, treat it as unknown. The unknown class exists so that a system with no evidence cannot send the confident version of anything.
| Class | Retry? | Ask for a new card? | Automated email? |
|---|---|---|---|
| Retriable | Yes, timed | No | Yes (retriable copy) |
| Needs a new card | Never | Yes | Yes (new-card copy) |
| Bank refused | Once | Only if it fails again | Yes (neutral copy) |
| Hard stop | Never | A person asks | No |
| Unknown | Once | Offer both routes | Yes (neutral copy) |
The same card may work later. Timing is the lever. Never ask for a new card here; the card is not the problem, and telling the customer it is sends them to fix something that is not broken.
Automated email: yes, the “retriable” variant. It says the bank declined for a temporary reason and the charge may well go through on another attempt.
| Code | What the bank means | What to do |
|---|---|---|
| insufficient_funds | The card works, but the balance was short at the moment it was charged. | Retry near the next payday window (the 1st or 15th). Do not ask for a new card. |
| card_velocity_exceeded | The card hit its spending or frequency limit. | Wait and retry. Repeated quick attempts keep hitting the same limit. |
| withdrawal_count_limit_exceeded | The card exceeded its allowed number of withdrawals. | Wait for the window to reset, then retry. |
| processing_error | A temporary processing error. Nothing is wrong with the card. | Retry within hours. |
| try_again_later | The issuer asked for the charge to be tried again later. | Retry within hours. |
| issuer_not_available | The issuing bank could not be reached. | Retry within hours. |
| reenter_transaction | The issuer could not process it and asked for it to be re-submitted. | Retry within hours. |
Only the customer can fix this. Every retry against the same card fails the same way, so retrying is not persistence, it is noise in the customer's bank statement.
Automated email: yes, the “needs a new card” variant. It says the card on file cannot be charged any more and that retrying the same card will not help.
| Code | What the bank means | What to do |
|---|---|---|
| expired_card | The card has expired. | Ask for a new card. Never retry. |
| incorrect_number | The card number on file is wrong. | Ask for a new card. |
| incorrect_cvc | The security code is wrong. | Ask for the card to be re-entered. |
| invalid_cvc | The security code is invalid. | Ask for the card to be re-entered. |
| invalid_expiry_month | The expiry month on file is invalid. | Ask for the card to be re-entered. |
| invalid_expiry_year | The expiry year on file is invalid. | Ask for the card to be re-entered. |
| invalid_account | The account behind the card is not valid. | Ask for a different card. |
| currency_not_supported | The card cannot be charged in this currency. | Ask for a different card, or bill in a supported currency. |
| card_not_supported | The card does not support this kind of purchase. | Ask for a different card. |
The card looks valid and the issuer said no. A retry is a coin flip. The answer usually lives at the bank, and only the cardholder can ask.
Automated email: yes, but the neutral variant. It names no reason, because none is known, and offers both routes: retry, or a different card if it keeps failing.
| Code | What the bank means | What to do |
|---|---|---|
| do_not_honor | The issuing bank refused and gave no reason. | One retry, timed to a payday window. If it fails again, a person should call. |
| generic_decline | The bank declined without giving a reason. | One retry. If it fails again, a person should reach out. |
| transaction_not_allowed | The bank does not permit this type of transaction on this card. | Do not retry. Ask for a different card. |
| service_not_allowed | The bank does not permit this service on this card. | Do not retry. Ask for a different card. |
| authentication_required | The bank requires the cardholder to authenticate this payment (3D Secure). | Send a payment link so the customer can complete authentication themselves. |
Retrying is useless and automated contact is inappropriate. A person should contact the customer, and the tone of that contact is not “your payment failed”.
Automated email: NO. A “click here to pay” message to somebody whose card was stolen, or who has revoked authorisation, is the wrong message from the wrong sender.
| Code | What the bank means | What to do |
|---|---|---|
| lost_card | The card was reported lost. The issuer has blocked it. | Stop automated retries and outreach. A person contacts the customer. |
| stolen_card | The card was reported stolen. The issuer has blocked it. | Stop everything automated. A person contacts the customer. |
| pickup_card | The issuer has flagged the card and asked for it to be retained. | Stop everything automated. A person contacts the customer. |
| fraudulent | The payment was declined as suspected fraud. | Stop. Review before any contact; the cardholder may not be your customer. |
| merchant_blacklist | The payment was blocked by a merchant-level block list. | Stop. This is between the issuer and your Stripe account, not the customer. |
| revocation_of_authorization | The cardholder revoked authorisation for recurring charges on this card. | Stop. The customer made a decision; a person should ask why. |
| revocation_of_all_authorizations | The cardholder revoked authorisation for all recurring charges. | Stop. Treat as a cancellation until a person hears otherwise. |
| stop_payment_order | The cardholder placed a stop-payment order on this charge. | Stop. A person should contact the customer. |
Some failures arrive with no decline code: the invoice ledger was never loaded, or the failure came through an events feed as a boolean. The temptation is to assume the benign case and send the “your card probably expired” email. Do not. The neutral message claims no reason, offers both routes (retry, or a different card if it keeps failing), and asserts neither. A customer told to replace a card that works stops believing the emails.
For the retriable and bank-refused classes, when you retry matters more than how often. Balances refill on paydays, and the 1st and 15th are when most do. So the retry, or the reminder that gets the customer to pay it themselves, should land on the next 1st or 15th that is at least 24 hours out, so a failure on the 14th does not trigger an attempt the customer has had no chance to fund. This is a heuristic and should be labelled as one wherever it shows up; whether it beats a fixed interval on a given book is something to measure with a holdout, not assume.
One constraint worth stating plainly: if your recovery tool reads Stripe read-only, it does not reschedule Stripe's own retries. The retry schedule belongs to your Stripe Billing settings. What the tool can time is its outreach, the nudge that gets the customer to settle the invoice through a pay link. A product that claims to be retrying cards it cannot retry is describing work it does not do.
Stripe decides. Smart Retries re-attempts the charge on its schedule, and the subscription settings decide whether a subscription that stays unpaid is cancelled, paused, or left unpaid. Because that date lives in Stripe, any dunning email sent from another tool should not state a cancellation date it does not control. The five-touch dunning sequence says access will stop; it never says when.
A Stripe subscription payment fails for one of five kinds of reason, and the decline_code on the failed charge says which: the balance or a limit was short at that moment (insufficient_funds, card_velocity_exceeded), the card itself is unusable (expired_card, incorrect_number), the bank refused without saying why (do_not_honor, generic_decline), the cardholder or issuer has blocked it (lost_card, stolen_card, fraudulent, revocation_of_authorization), or there is no code at all. The class, not the individual code, decides what to do next.
insufficient_funds means the card is valid but the balance was short at the moment Stripe charged it. It is the most recoverable decline there is: retry near the next payday window, typically the 1st or the 15th, and do not ask the customer for a new card, because the card is not the problem. Asking for a new card here sends the customer to fix something that is not broken.
Stripe decline codes that should never be retried are expired_card, incorrect_number, incorrect_cvc, invalid_cvc, invalid_expiry_month, invalid_expiry_year, invalid_account, currency_not_supported and card_not_supported, because the card itself cannot work and every retry fails the same way, plus the hard-stop codes lost_card, stolen_card, pickup_card, fraudulent, merchant_blacklist, revocation_of_authorization, revocation_of_all_authorizations and stop_payment_order, where retrying is useless and automated contact is inappropriate.
No. An automated failed-payment email is right for retriable declines, unusable cards and bank refusals, but wrong for the hard-stop codes. A message saying "your payment failed, click here to pay" sent to somebody whose card was reported stolen, or who has revoked authorisation for recurring charges, is the wrong message from the wrong sender, and it is the kind of thing a customer remembers. Those cases need a person.
Stripe cancels, pauses or leaves unpaid a subscription after failed payments according to the account's Billing settings: Smart Retries attempts the charge on a schedule for up to a few weeks, and the subscription settings decide what happens when the last retry fails. Because that lapse date is set in Stripe, a dunning email sent by any other tool should not state a cancellation date it does not control.
do_not_honor means the issuing bank refused the charge and gave no reason; the card itself looks valid. It is the most common decline code and the least informative. One retry is reasonable, ideally timed to a payday window. If it fails again, a person should contact the customer, because the answer usually lives at the bank and only the cardholder can ask.
The classification is the one Exeechain applies to every failed invoice it reads from Stripe, and it decides three things for each one: whether an automated email may go out, which of three copy variants it uses, and when it lands. Codes are Stripe's own decline_code values; anything absent from the table falls through to unknown and is treated conservatively. The failed payment recovery page shows what the sequence looks like on a real book.
Keep reading
Customer success
10 min read · Sep 17, 2026
Retention metrics
9 min read · Sep 17, 2026
Retention metrics
9 min read · Sep 17, 2026
First scores in 15 minutes. Full accuracy in 24 hours. From $299/mo. Read-only Stripe connection, decline-code routing, five-touch recovery.
Not ready to switch? Size your leak from your MRR and churn rate, with no billing access at all.