# Stripe card_declined: route each decline code to the right recovery
When Stripe declines a card, the error has a `decline_code`. Treating every decline the same wastes retries on cards that will never succeed and misses the recovery window on ones that would.
## The symptom
`card_error` with type `card_declined`, or a PaymentIntent whose `last_payment_error.decline_code` is set. In webhooks: `payment_intent.payment_failed` or `invoice.payment_failed`.
## Confirm the cause
Read the code, not just the message:
```js
const code = paymentIntent.last_payment_error?.decline_code;
```
## The routing table
**Soft declines, retry with timing:**
- `insufficient_funds` - the big one. Retrying immediately works ~15-20% of the time; retrying around day 3 works much better (payday effect). Never treat as hard.
- `processing_error` - Stripe or issuer hiccup. Retry soon, it often clears.
- `do_not_honor` - ambiguous. One retry is fine; if it repeats, treat as hard and ask for a new card.
- `card_velocity_exceeded` - spending limit hit. Retry after a day.
**Hard declines, do not retry, ask for a new payment method:**
- `lost_card`, `stolen_card` - the card is dead. Every extra retry is a failed charge the issuer notices.
- `expired_card` - one retry max (the updater may have fixed it silently), then ask for new details.
- `incorrect_cvc` - let the customer re-enter, do not auto-retry.
**Authentication, never retry silently:**
- `authentication_required` - the payment needs 3D Secure. Retrying the same call fails identically. Send the customer through authentication instead.
## The fix
Branch on the code in your webhook handler:
```js
if (event.type === 'invoice.payment_failed') {
const code = event.data.object.payment_intent?.last_payment_error?.decline_code;
if (code === 'authentication_required') return sendReauthLink(customer);
if (['lost_card','stolen_card','expired_card'].includes(code)) return askForNewCard(customer);
if (code === 'insufficient_funds') return scheduleRetry(customer, {inDays: 3});
return scheduleRetry(customer, {inDays: 1});
}
```
## Verify the fix
In test mode, force each code with Stripe's test cards (4242 4242 4242 4242 succeeds, 4000 0000 0000 0002 declines generically, 4000 0000 0000 9995 declines with insufficient_funds) and confirm each routes to the right branch. Check your failed-payment queue weekly: no hard-decline code should show more than one retry attempt.