The distinction

SOFT

Temporary no

The card is valid and active. Not enough money, a limit, a slow issuer, a risk rule that fired on this attempt.

Response: a well-timed retry
HARD

Permanent no

Expired, lost, stolen, closed, invalid or restricted. The same details will always fail.

Response: new information, never a retry

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

CodeMeaningRetry guidance
51Insufficient fundsWhen funds are likely back. Respect MAC 24 to 30 if present. Best case: a network signal.
61Exceeds withdrawal amount limitAfter the limit resets, usually next day
65Exceeds withdrawal frequency limitAfter the frequency window resets
05Do not honorOnce, after a day or more, unless MAC 03 accompanies it
91Issuer or switch inoperativeAfter a short delay
96System malfunctionAfter a short delay
92Unable to routeAfter a short delay
MAC 02Cannot approve at this time, try laterAfter 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

CodeMeaningWhat to do
54Expired cardAccount updater, then retry with new expiry
41Lost cardRemove credential; account updater may return replacement
43Stolen cardRemove credential; never retry
04Capture cardRemove credential; never retry
14Invalid card numberAsk customer to re-enter
57Transaction not permitted to cardholderDifferent card
62Restricted cardDifferent card
63Security violationDo not machine-retry
79Life cycle (Mastercard)Account updater
82Policy (Mastercard)Check MAC; usually stop
83Fraud/security (Mastercard)Stop
MAC 03Do not try againStop; fee on every further attempt
MAC 21Recurring payment cancelledStop 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:

  1. On every decline, store the response code and, on Mastercard, the Merchant Advice Code.
  2. 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.
  3. 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.
  4. Cap total attempts per payment well below the network limits, and stop early if the customer updates their card.
  5. 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.