The distinction
A soft decline is a temporary no. The card is valid and active, and something about this specific attempt failed: not enough money, a spending limit, a slow issuer, a risk rule that fired on this transaction. Ask again under different conditions and it can approve.
A hard decline is a permanent no, at least with the credentials you have. The card is expired, lost, stolen, closed, invalid, or restricted from this kind of use. Asking again with the same details will always fail.
The distinction matters because the right response to each is opposite. Soft declines want a well-timed retry. Hard declines want new information: refreshed card details from an account updater, or a new payment method from the customer. Retrying a hard decline is worse than useless, since the networks penalise it.
Soft decline codes
| Code | Meaning | Retry guidance |
|---|---|---|
| 51 | Insufficient funds | When funds are likely back. Respect MAC 24 to 30 if present. Best case: a network signal. |
| 61 | Exceeds withdrawal amount limit | After the limit resets, usually next day |
| 65 | Exceeds withdrawal frequency limit | After the frequency window resets |
| 05 | Do not honor | Once, after a day or more, unless MAC 03 accompanies it |
| 91 | Issuer or switch inoperative | After a short delay |
| 96 | System malfunction | After a short delay |
| 92 | Unable to route | After a short delay |
| MAC 02 | Cannot approve at this time, try later | After at least a day |
Stripe’s equivalents are insufficient_funds, card_velocity_exceeded, withdrawal_count_limit_exceeded, do_not_honor, generic_decline, try_again_later, issuer_not_available and processing_error.
Hard decline codes
| Code | Meaning | What to do |
|---|---|---|
| 54 | Expired card | Account updater, then retry with new expiry |
| 41 | Lost card | Remove credential; account updater may return replacement |
| 43 | Stolen card | Remove credential; never retry |
| 04 | Capture card | Remove credential; never retry |
| 14 | Invalid card number | Ask customer to re-enter |
| 57 | Transaction not permitted to cardholder | Different card |
| 62 | Restricted card | Different card |
| 63 | Security violation | Do not machine-retry |
| 79 | Life cycle (Mastercard) | Account updater |
| 82 | Policy (Mastercard) | Check MAC; usually stop |
| 83 | Fraud/security (Mastercard) | Stop |
| MAC 03 | Do not try again | Stop; fee on every further attempt |
| MAC 21 | Recurring payment cancelled | Stop billing; contact customer |
Stripe’s equivalents include expired_card, lost_card, stolen_card, pickup_card, invalid_number, card_not_supported, restricted_card and security_violation.
The grey areas
Three responses sit between the groups and cause most of the arguments.
Do not honor (05). The issuer declined and did not say why. It could be a temporary risk flag, an issuer rule, or something permanent. Treat it as soft with a strict cap: one retry after a day or more. On Mastercard, the Merchant Advice Code resolves the ambiguity, and you should store and act on it.
Generic decline. Stripe returns generic_decline when the issuer gave no reason and also when Radar blocked the payment. Check for a Radar block first; if there is none, treat it like do not honor.
Merchant Advice Code 01, new account information available. Technically the decline is hard with the details you have and soft with the right ones. Query the account updater, then retry with the refreshed credential. Do not retry with the old one.
Why insufficient funds is the soft decline that matters
Soft declines are where the recoverable revenue is, and one soft decline dominates. Mastercard’s data puts insufficient funds at around 60 percent of recurring-payment declines. It is also the soft decline where timing is the entire game: the only thing that changes between the failed attempt and a successful one is money arriving in the account.
Every other soft decline resolves on its own schedule. An issuer outage clears in hours. A daily limit resets at midnight. Insufficient funds resolves when the cardholder gets paid, and you do not know when that is. A fixed day-3, day-7 schedule guesses. A machine-learned scheduler guesses better. A network signal does not guess: Mastercard’s Payment Optimization Platform watches the card after the decline and signals when conditions indicate a retry is more likely to approve. That signal is what PaymentRetryAlert delivers to your webhook.
Building retry logic around the split
A minimal implementation, in order:
- On every decline, store the response code and, on Mastercard, the Merchant Advice Code.
- If the code is in the hard group or the MAC is 03 or 21, stop retries for that payment method. Trigger account updater on lifecycle codes; otherwise trigger customer outreach.
- If the code is soft, schedule a retry with a delay appropriate to the code. Issuer-unavailable codes get hours; limit codes get a day; insufficient funds gets the issuer’s advised wait if a MAC 24 to 30 was returned, or a network signal if the card is under monitoring, or a payday heuristic as a fallback.
- Cap total attempts per payment well below the network limits, and stop early if the customer updates their card.
- Report recovery rate by decline code. It will show you which group is leaking.
Most billing platforms make step 3 the hard part, because they expose a single retry schedule regardless of code. Where that is the case, the workaround is to pause the platform’s schedule on hard declines and to add an external trigger for insufficient-funds retries.
- Stripe, Card declines
- Mastercard, Decline details and Merchant Advice Codes