Cardnudge

Stripe decline codes, explained family by family

Last reviewed 2026-10-07

Stripe returns a short string when a card is refused. Most guides list them alphabetically, which is useless when you are trying to decide what to do. What matters is which of three families a code belongs to, because each family has a different answer: get a new card, wait and retry, or give up. This is the table our own audit runs on.

First: decline_code and failure_code are not the same field

This caught us while building, and it is the single most common reason a decline-handling integration misses failures. A refused card can carry two different codes, in two different fields, and the specific one is not always where you expect.

Take two of Stripe's own test cards. The insufficient-funds card returns failure_code card_declined — generic and useless — with the real reason, insufficient_funds, in decline_code. The expired-card test card does the opposite: its error code is expired_card and its decline_code column in Stripe's testing table reads not applicable. Read only decline_code and every expired card looks like an unclassified failure.

The rule we settled on: read decline_code first because it is more specific, then fall back to failure_code, and never classify on the generic card_declined value alone. Our classifier does exactly this, and the behaviour is covered by unit tests.

Family one: the card needs updating

The card on file is wrong, expired, or has been replaced. The money may well exist. Retrying the same card cannot work, because nothing about the card will change on its own.

  • expired_card
  • expired_payment_information
  • incorrect_address
  • incorrect_cvc
  • incorrect_number
  • incorrect_zip
  • invalid_cvc
  • invalid_expiry_month
  • invalid_expiry_year
  • invalid_number
  • new_account_information_available

What to do: ask the customer for a new card. This is the family that responds to emails and a one-click update page, and it is the only family where a well-written message beats a better retry schedule. Stripe's automatic card updater also handles a slice of this on its own, refreshing stored details when an issuer reissues a number.

Family two: insufficient funds

The card is valid and the details are right. The money was not there at that moment, or a limit was hit.

  • card_velocity_exceeded
  • insufficient_funds
  • payment_limit_exceeded
  • withdrawal_count_limit_exceeded

What to do: retry, at a better moment. This is the family retry engines exist for, and the family where Stripe's Smart Retries earns its keep — it picks retry times from a model rather than a fixed rule. Chasing the customer here is usually worse than waiting: a payday or a cleared balance fixes it without anyone being embarrassed.

Family three: hard declines

The issuer refused and will keep refusing. The card is reported lost or stolen, the payment was flagged as fraudulent, or the account is restricted.

  • card_not_supported
  • compliance_violation
  • currency_not_supported
  • do_not_honor
  • fraudulent
  • invalid_account
  • lost_card
  • lost_or_stolen_card
  • merchant_blacklist
  • not_permitted
  • pickup_card
  • restricted_card
  • revocation_of_all_authorizations
  • revocation_of_authorization
  • security_violation
  • service_not_allowed
  • stolen_card
  • stop_payment_order
  • testmode_decline
  • transaction_not_allowed

What to do: nothing automated. Retrying is wasted, and for several of these Stripe's own guidance is to show the customer a generic message rather than the real reason — telling someone their card was reported stolen is not a conversation you want an automated email to start. Treat this family as churn that already happened and move on.

Everything else

Our table classifies 35 codes. Anything Stripe returns that is not in it falls to a fourth bucket we call Other, and we count none of it as recoverable.

generic_decline sits here deliberately. Stripe defines it as the card being refused for an unknown reason, or blocked by Radar. We could file it under hard declines and the arithmetic would not change, since both carry a zero recovery coefficient. We leave it in Other because that is the truthful label: we do not know why it failed, and saying so is more useful than a confident guess.

The nine codes Stripe will never retry

Separate from the families, Stripe documents a list of codes it refuses to retry automatically. Scheduled attempts keep counting and the invoice's attempt count keeps rising, but no charge is made until a new payment method appears.

  • incorrect_number
  • lost_card
  • pickup_card
  • stolen_card
  • revocation_of_authorization
  • revocation_of_all_authorizations
  • authentication_required
  • highest_risk_level
  • transaction_not_allowed

Note that incorrect_number appears here but belongs to the card-needs-updating family: it is recoverable, just not by retrying. That distinction is the whole point of splitting families from retry behaviour.

Questions people ask

What is the difference between decline_code and failure_code in Stripe?
failure_code is the error code on the charge and is often the generic card_declined. decline_code carries the issuer's specific reason, such as insufficient_funds. Some failures populate only one of the two — an expired card surfaces as the error code expired_card with no decline_code — so a correct integration reads decline_code first and falls back to failure_code.
Which Stripe decline codes are worth retrying?
Mainly the insufficient-funds family, where the card is valid and a later attempt can succeed. Cards that need updating cannot be fixed by retrying, and hard declines such as lost_card or stolen_card will be refused every time.
What does generic_decline mean?
Stripe defines it as a refusal for an unknown reason, or a payment blocked by Radar. There is no specific action it points to, which is why we count it as unrecoverable rather than pretending it suggests a fix.
Why does Stripe not retry a lost or stolen card?
Because the issuer will refuse every attempt. Stripe lists nine non-retryable codes and keeps the attempt counter running without executing a charge, so the invoice only moves once a different payment method is added.

Sources

Find out your exact number

A free, read-only audit of your own Stripe account. No signup, and the key never leaves your browser.

Run a free audit

Keep reading