Core Concepts
How It Works
Savby intercepts Stripe payment failure webhooks, maps the decline code to the right recovery action, and executes it automatically — without any changes to your application code.
The recovery pipeline
Stripe webhook received
When an invoice payment fails, Stripe sends an invoice.payment_failed event to Savby. The webhook signature is verified before any processing begins. Duplicate events are rejected using an idempotency check on the Stripe event ID.
Decline code mapped to strategy
Savby reads the decline code from the invoice and looks it up in its decline-code table. Each code maps to one of three recovery strategies: RETRY (scheduled charge retries), NOTIFY_NO_RETRY (email the customer to update their card), or MANUAL (flag for human review).
Retry schedule built and queued
Based on the strategy, Savby builds a sequence of actions — charge retries, customer emails, or manual flags — each with a precise scheduled time. These are stored as RecoveryAction records and enqueued in BullMQ with the appropriate delay.
Three recovery categories
Every Stripe decline code falls into one of three categories. The category determines which actions Savby takes:
Temporary failures. Savby schedules charge retries at optimal intervals — giving card networks and banks time to recover.
insufficient_fundscard_declinedprocessing_errorand more →Card data is wrong or expired. Retrying will never work — the customer must update their payment method. Savby emails them immediately.
expired_cardincorrect_cvcinvalid_expiry_yearand more →High-risk codes like lost or stolen cards. Automated recovery is inappropriate — Savby flags the case for human review.
lost_cardstolen_cardfraudulentand more →See the full list in the Decline Codes reference.
Retry schedule examples
Savby adapts the retry schedule to the specific decline code:
| Decline code | Retry 1 | Retry 2 | Retry 3 |
|---|---|---|---|
insufficient_funds | T + 24 h | T + 72 h | T + 7 d |
card_declined | T + 4 h | T + 8 h | T + 24 h |
processing_error | T + 1 h | T + 2 h | T + 24 h |
do_not_honor | T + 24 h | T + 48 h | T + 24 h |
T = time of the original payment failure.
Idempotency guarantee
Every charge retry uses a unique Stripe idempotency key derived from the recovery action ID. If a retry job runs more than once (e.g., after a worker crash), Stripe will return the original response without double-charging the customer. Duplicate Stripe webhook events are also deduplicated using a unique constraint on the event ID in the database.