Free reference
Stripe decline codes explained
Every card decline code Stripe returns, what it actually means, what to do about it, and the nine that Stripe will never successfully retry.
The nine codes Stripe will not retry
This is the part most people get wrong, so it is worth stating precisely. Stripe documents a specific set of hard decline codes. When one of these comes back, Stripe still schedules retries and still increments attempt_count, but the retry does not actually execute until a new payment method is attached.
- incorrect_number
- lost_card
- pickup_card
- stolen_card
- revocation_of_authorization
- revocation_of_all_authorizations
- authentication_required
- highest_risk_level
- transaction_not_allowed
In Stripe's own words: "For these failures, the scheduled retries continue but the payment only executes if you obtain a new payment method."
The practical consequence is that your retry count is not your recovery opportunity. If a chunk of your failures are these codes, no amount of retrying moves the number. Only getting a new card does.
The expired card trap
expired_card is not on Stripe's hard decline list. That means scheduled retries keep firing against a card that has expired.
They cannot succeed. An expired card needs a new number, not another attempt. So the retries burn through your schedule, increment the attempt count, and change nothing.
This is the clearest example of why decline codes matter more than retry volume. An expired card is an email problem, not a retry problem. The two things that actually fix it are prompting the customer to update their card, and Stripe's Card Account Updater refreshing it automatically where the issuer participates.
If you take one operational change from this page, make it this: route expired_card straight to a customer email rather than leaving it to the retry schedule.
Which declines are worth retrying
Outside the nine hard codes, the useful mental split is whether the reason is temporary.
- Genuinely worth retrying:
insufficient_funds,processing_error,issuer_not_available,try_again_later,reenter_transaction,approve_with_id. The card is fine, the moment was wrong. Timing is what recovers these, and payday proximity matters more than attempt count for insufficient funds. - Retryable in principle, but a data problem in practice:
incorrect_cvc,incorrect_zip,invalid_expiry_month,invalid_expiry_year. Retrying the same wrong details will fail identically. The customer has to correct something. - Not worth retrying: the nine hard codes above, plus
expired_cardfor the reason described.
A note on insufficient_funds. You will find sources that class this as a hard decline. It is not on Stripe's list of codes that block retries, and treating it as unrecoverable is a mistake. It is among the most recoverable declines there is, provided you retry when money is likely to be present rather than a few hours later.
Codes you should not explain to the customer
Stripe advises presenting certain declines generically rather than specifically. The reasoning is straightforward: if a card has been reported stolen, the person holding it may be the thief, and a precise error message is useful to them.
Present these as a plain "your card was declined, please try another payment method": fraudulent, lost_card, stolen_card, merchant_blacklist, pickup_card, restricted_card, highest_risk_level.
For benign codes such as expired_card, incorrect_cvc or incorrect_zip, being specific genuinely helps, so say what is wrong.
Finding your own decline codes
- In the Stripe dashboard, open Payments and filter to Failed.
- Open any failed payment. The decline code is on the payment detail page.
- Through the API, read
decline_codeon the charge outcome. It is more specific thanfailure_code, which is the broader category. - For subscriptions specifically, listen for the
invoice.payment_failedwebhook and read the decline code off the attached charge.
What you are looking for is the distribution. If most of your failures are insufficient_funds, better retry timing will move real money. If most are expired_card or the hard codes, retry tuning will do nothing and your effort belongs in customer emails instead.
Frequently asked questions
Which Stripe decline codes cannot be retried?
Nine: incorrect_number, lost_card, pickup_card, stolen_card, revocation_of_authorization, revocation_of_all_authorizations, authentication_required, highest_risk_level, and transaction_not_allowed. Stripe still schedules retries for these, but they only execute once a new payment method exists.
Does Stripe keep retrying an expired card?
Yes. expired_card is not on the hard decline list, so retries keep firing. They cannot succeed without a new card number, which is why an expired card should trigger an email rather than another attempt.
What is the difference between a hard and a soft decline?
A soft decline is temporary and the same charge may work later. A hard decline means the issuer has rejected the card in a way retrying will not fix. Stripe's own definition of a hard decline code is one where the issuing bank has rejected the transaction and you cannot retry it.
Where do I find decline codes in my Stripe dashboard?
Payments, filtered to Failed. Open any failed payment and the code is on the detail page. Via the API it is the decline_code field on the charge outcome.
Should I show the decline reason to my customer?
Not for fraud-related codes, which Stripe advises presenting as a generic decline. For benign ones such as an expired card or a wrong CVC, being specific helps them fix it.
See your own decline mix
RecoverFlow scans your last 90 days of Stripe invoices and breaks your failures down by decline reason, so you can see whether retry timing or customer emails is where your money actually is. Free, no card required.
Run the free 90 day scanCompiled from Stripe's published documentation on decline codes and revenue recovery as of 27 July 2026. Stripe occasionally adds or deprecates codes, so treat your own dashboard as the source of truth. Spotted something wrong? Email admin@recoverflow.org and it gets fixed.
Recover