Stripe decline codes are the values Stripe puts in outcome.reason on a Charge and in decline_code on a card error. There are fifty of them for cards, and most of your volume lands on the ones that say nothing: Stripe's own documentation notes that issuers categorize most declines as generic.
This page is a lookup. What each code means, what to put in front of the customer, whether a retry can work, and which combinations of codes indicate that someone is running stolen cards through your checkout rather than that your customers are having a bad day.
Every meaning below is Stripe's own, from the decline codes reference.
Three ways a Stripe payment fails
Before you read the code, read outcome.type. Stripe lists three reasons a payment fails: an issuer decline, a blocked payment, and an invalid API call.
type: "issuer_declined" means the request reached the card network and the issuing bank said no. network_status is declined_by_network, and network_decline_code holds the raw 2-4 digit code the bank returned.
type: "blocked" means Stripe stopped it. Radar or Adaptive Acceptance rejected the payment and, in Stripe's words, "it doesn't obtain authorization from the card issuer." network_status is not_sent_to_network and both network code fields are null.
type: "invalid" means your API call was malformed. These usually do not produce a payment in the Dashboard at all.
The distinction matters because generic_decline appears under both of the first two. Same code, two completely different causes.
In the API, read last_payment_error.decline_code on the PaymentIntent, or outcome.reason on the Charge. A declined card raises a card_error; some codes surface as the error code itself, as in Stripe's own example returning "code": "incorrect_cvc" with "param": "cvc".
The card decline codes
Meanings and next steps below are Stripe's. The retry column is the practical read of them.
| Code | What it means | Show the customer | Retry |
|---|---|---|---|
generic_decline | Unknown issuer reason, or Radar / Adaptive Acceptance blocked the payment | Generic decline message | No |
do_not_honor | Unknown issuer reason | Generic decline message | No |
call_issuer, no_action_taken, revocation_of_authorization, revocation_of_all_authorizations, security_violation, service_not_allowed, stop_payment_order, transaction_not_allowed | All documented as "declined for an unknown reason" | Generic decline message, suggest contacting the bank | No |
not_permitted | The payment isn't permitted | Generic decline message, suggest contacting the bank | No |
insufficient_funds | Not enough funds or credit | "Not enough funds. Try another card." | No |
card_velocity_exceeded | Balance, credit limit, or transaction amount limit exceeded | "Try another card." | No |
withdrawal_count_limit_exceeded | Balance or credit limit exceeded | "Try another card." | No |
incorrect_cvc / invalid_cvc | CVC wrong | "Check the security code." | Yes, after correction |
incorrect_number / invalid_number | Card number wrong | "Check the card number." | Yes, after correction |
invalid_expiry_month / invalid_expiry_year | Expiry wrong | "Check the expiry date." | Yes, after correction |
expired_card | Card has expired | "That card has expired." | No |
incorrect_zip | Postal code wrong | "Check the billing postal code." | Yes, after correction |
incorrect_address | Address wrong | "Check the billing address." | Yes, after correction |
fraudulent | Stripe suspects the payment is fraudulent | Generic decline message only | No |
lost_card | Reported lost | Generic decline message only | No |
stolen_card | Reported stolen | Generic decline message only | No |
pickup_card | Customer can't use this card, possibly reported lost or stolen | Generic decline message | No |
merchant_blacklist | Matched a value on your own Stripe block list | Generic decline message only | No |
restricted_card | Customer can't use this card, possibly reported lost or stolen | Generic decline message | No |
card_not_supported | Card doesn't support this type of purchase | "This card can't be used for this purchase." | No |
currency_not_supported | Card doesn't support the specified currency | "This card can't be charged in ." | No |
invalid_account, new_account_information_available | Card, or the account behind it, is invalid | "Contact your bank." | No |
invalid_amount | Amount invalid or above what's allowed | Check your amount first | Only if your amount was wrong |
duplicate_transaction | Identical amount and card submitted very recently | "You may already have been charged." | No, check for an existing payment |
authentication_required | Needs 3D Secure | Run the authentication flow | Yes, with 3DS |
authentication_not_handled | You proceeded without the required authentication | Run the 3DS/SCA flow | Yes, with 3DS |
mobile_device_authentication_required | Wallet tap needs re-authentication | "Tap your device again." | Yes |
issuer_not_available | Stripe couldn't reach the issuer | "Something went wrong, try again." | Yes, once |
processing_error | Error while processing the card | "Something went wrong, try again." | Yes, then later |
reenter_transaction | Issuer couldn't process it, reason unknown | "Something went wrong, try again." | Yes, once |
approve_with_id | Payment can't be authorized | "Something went wrong, try again." | Yes, once |
testmode_decline | A Stripe test card number was used | "Use a real card." | No |
Card-reader codes (incorrect_pin, invalid_pin, offline_pin_required, online_or_offline_pin_required, pin_try_exceeded) apply to Terminal, not to web checkout. do_not_try_again and try_again_later are listed as deprecated decline codes; both names live on as advice codes, which is a separate field.
Local payment methods have their own set in the same reference, and the two lists share names. partner_generic_decline, payment_limit_exceeded, and invalid_billing_agreement are LPM-only, but insufficient_funds, expired_card, processing_error, invalid_amount, currency_not_supported, and duplicate_transaction appear in both. The LPM versions map to a different charge outcome reason: an LPM insufficient_funds produces partner_insufficient_funds on the outcome, not the card meaning. Branch on the payment method type before you branch on the code.
Reading Stripe's generic_decline and do_not_honor
These two are most of your decline volume and neither tells you anything. Stripe is direct about it: issuers categorize most declines as generic, and for privacy and security reasons they discuss the specifics of a decline only with their cardholders.
So look at what came with it. Stripe's own suggestion for a generic or do_not_honor decline is to examine the associated data: if the CVC or AVS check failed, that is your actual signal, and asking the customer to verify both before you charge again beats asking them to call their bank.
The other thing to check is whether the issuer saw the payment at all. A generic_decline with network_status: "not_sent_to_network" is Stripe blocking, not a bank declining, and nothing your customer says to their issuer will change it. That case is covered in more detail in the Stripe Radar walkthrough.
Advice codes and how many times you may retry
Alongside the decline code, outcome.advice_code tells you what to do next. There are three values.
| Advice code | Meaning |
|---|---|
do_not_try_again | Don't use this card again for the same transaction |
try_again_later | The issuer declined, but you can retry |
confirm_card_data | Some of the information provided is incorrect; have the customer validate their card |
There is also network_advice_code, the raw 2-4 digit code from the issuer. Mastercard calls these Merchant Advice Codes. The meaning differs by network, so read it together with the card brand.
Retries are metered, and the two ceilings you care about are not the same number. Stripe recommends a maximum of eight retries for charges that permit them, and warns that additional retries can look like fraud to issuers, which raises declines on your legitimate charges. Visa's rules are looser but sharper-edged: its April 2021 response code rules let a merchant reattempt a Category 2 decline ("issuer cannot approve at this time") up to 15 times in 30 days, while a Category 1 decline ("issuer will never approve") permits no reattempt at all. Stripe's eight is the safer number to build against.
What to show the customer
Four buckets cover every code.
Correctable data (incorrect_cvc, incorrect_number, invalid_expiry_*, incorrect_zip, incorrect_address). Name the field. This is the only class where you should send the customer back into the form.
Funds and limits (insufficient_funds, card_velocity_exceeded, withdrawal_count_limit_exceeded). Say the card was declined and offer another payment method. Do not retry on a timer.
Authentication (authentication_required, authentication_not_handled). Don't write copy for these. Run the 3DS flow.
Everything else. One generic string: "Your card was declined. Contact your bank or try another card." Stripe explicitly instructs you to present fraudulent, lost_card, stolen_card, and merchant_blacklist this way. Telling a card tester that the card is reported stolen tells them exactly what they wanted to know, for free.
The code mix that means card testing
A legitimate decline stream is spread across many cards, many customers, and codes weighted toward insufficient_funds and generic_decline, at your normal amounts, at your normal traffic shape.
Card testing looks different in four ways at once, and no single one of them is conclusive.
Distinct cards per entity. One browser, one IP, or one new account attempting five, twenty, or two hundred different card numbers. This is the strongest single indicator, because no real customer has forty cards.
Small or zero amounts. Stripe notes that testers prefer card setup because "card validation and authorizations during card setup don't typically show up on cardholder statements", and that when they use payments they "create small amount payments, which cardholders are less likely to notice and report as fraudulent."
A burst. Minutes, not hours. Stripe's own identification guide points at a spike in failed or blocked payments and a spike in 402 errors on the Developers page.
A code mix without a fix. generic_decline and do_not_honor dominate, mixed with incorrect_cvc (the tester has the number but is guessing the CVC) and insufficient_funds (the card is live, which is exactly what they were checking for). Notably absent: incorrect_number corrected on the second attempt, which is what a real fat-fingered customer produces.
If you have Radar with rules, the counters to build on are card_count_for_ip_address_hourly, card_count_for_email_daily, and declined_charges_per_ip_address_hourly, all in the supported attributes list. Read the rules reference for the semantics before you tune a threshold: counts exclude the payment currently being processed, so the first attempt in a window reads 0, and an hourly window is "up to 3900 seconds (5 minute buckets)" rather than a clean hour.
The per-IP versions are the weak ones. Testers rotate residential proxies, and shared corporate or mobile egress puts unrelated customers behind one address. Counting per device survives the rotation; velocity checks covers how the windows behave in practice.
This is the thing I built Portreeve for: a checkout_attempt verdict that reads velocity per card, per device, and per identity cluster before the charge is created, and returns allow, review, or block with reason codes in under 100 ms. The card probe rules are device-keyed, so the browser fingerprint snippet is what makes those shapes blockable at ordinary amounts; without a device token they top out at review. It is in open beta.
On the practitioner side, the recurring report is that you end up counting this yourself. In an Ask HN thread on card testing attacks, tinyprojects described rolling "a combination of IP-based bans on creating checkout links + notifications if a purchase has many failed attempts using diff cards," and a_simm reported that "cycling (all) the keys stopped it immediately," which points at the entry path Stripe itself names: your publishable key, used to retry payments without touching your site. The mechanics of that path are in card testing on Stripe.
One number worth knowing while you decide how aggressive to be: in the US, Stripe charges $15.00 for each dispute you receive, and another $15.00 if you respond to it manually, refunded only if you win. That is more than most of these test charges are worth.
Where Radar's blocks appear
A Radar block does not produce an issuer decline code. It produces an outcome with type: "blocked" and a reason describing the block:
"outcome": {
"network_decline_code": null,
"network_advice_code": null,
"network_status": "not_sent_to_network",
"reason": "highest_risk_level",
"advice_code": "do_not_try_again",
"risk_level": "highest",
"seller_message": "Stripe blocked this charge as too risky.",
"type": "blocked"
}
reason here carries the block reason instead of a network code. highest_risk_level is the Radar model. Accounts on IC+ pricing can also see low_probability_of_authorization with seller_message: "Stripe blocked this payment as it is unlikely to be authorized.", which is Adaptive Acceptance avoiding network costs rather than fraud prevention.
Blocked payments still show up as failures in your Dashboard, marked Blocked by Stripe, and seller_message is the field to log. Two blocks that both surface as generic_decline to the customer can have entirely different reason values internally.
One quirk: if a Radar rule uses a post-authorization attribute such as :cvc_check: or :address_zip_check:, Stripe has to exchange data with the issuer to evaluate it, so the customer can see a temporary authorization on their statement even though the charge was blocked. Stripe says it generally disappears after a few days.
Reproducing a decline in test mode
Each shape has a test card. Useful when you are writing the handler and want to see your own copy render.
| Card number | Result |
|---|---|
4000000000000002 | card_declined with decline_code: generic_decline |
4000000000009995 | card_declined with decline_code: insufficient_funds |
4000000000009987 | card_declined with decline_code: lost_card |
4000000000009979 | card_declined with decline_code: stolen_card |
4000000000000069 | error code: expired_card, no decline code |
4000000000000127 | error code: incorrect_cvc, no decline code |
4000000000000119 | error code: processing_error, no decline code |
4100000000000019 | always blocked by Radar as highest risk |
Note the split in that table: the first four come back as card_declined with a decline_code, while the next three surface the reason as the error code and leave decline_code unset. If your handler only reads decline_code, three of these fall through to your generic branch. Include a CVC when you test 4000000000000127, because Stripe skips the CVC check if you omit it.
The corresponding PaymentMethod tokens (pm_card_visa_chargeDeclinedInsufficientFunds, pm_card_chargeDeclinedIncorrectCvc, pm_card_radarBlock, and so on) work in server-side test calls without a card number. Full list in Stripe's testing reference.
Related references
- Stripe decline codes and network decline codes
- Declines for the three failure types and the
outcomeshapes - Card declines for advice codes and retry guidance
- Protect yourself from card testing for Stripe's own checklist
- Radar rules reference for velocity window semantics and post-authorization attributes
- Handling verdicts for the
allow/review/blockshape
If you want the decline stream counted for you before the charge is created, the free tier screens 1,000 events a month with no card: sign up.