Stop losing subscribers to failed payments you could have recovered
Cards fail for very different reasons: an expired card needs the customer, an empty account needs time, a lost card needs a new card and a gentle tone. Treating them the same wastes retries and annoys customers. Pick a Stripe decline code to see the recovery plan Dunning Engine would run, then run the open-source server with your own Stripe key. No account with us, no per-recovery fee.
Preview a recovery plan
Class:
| When (UTC) | What happens |
|---|
What is recovery worth to you?
Your numbers only: this page does not know your recovery rate and does not promise any uplift.
Extra revenue kept per month: -
Run it yourself
git clone https://github.com/Duncan-Haywood/fp-monorepo && cd fp-monorepo/ventures/dunning-engine STRIPE_SECRET_KEY=rk_live_... STRIPE_WEBHOOK_SECRET=whsec_... \ ADMIN_KEY=$(openssl rand -hex 16) LINK_SECRET=$(openssl rand -hex 16) \ PUBLIC_URL=https://dunning.example.com RESEND_API_KEY=re_... \ MAIL_FROM="Billing <billing@example.com>" COMPANY_NAME=Acme \ node server.mjs # DRY_RUN=true until you set DRY_RUN=false
Point a Stripe webhook at /webhook/stripe for invoice.payment_failed, invoice.paid, invoice.voided, invoice.marked_uncollectible, customer.subscription.deleted and payment_method.attached. Turn off Stripe's automatic retries so only one system charges. Worried a deploy will drop a webhook? Put webhook-relay in front of it. One-click Render deploy and Docker are in the README.
Questions
Why not just use Stripe Smart Retries?
Smart Retries is good and free to try, and for many businesses it is enough. Dunning Engine is for teams that want the retry schedule and the email wording under their own control, want expired or lost cards to skip pointless retries entirely, or do not want a per-recovery fee from a third-party recovery service. You must turn Stripe's own automatic retries off so the two do not both charge the customer.
Which declines should never be retried?
Hard declines such as lost_card, stolen_card, pickup_card and restricted_card, and unusable cards such as expired_card or incorrect_number. Retrying them only adds failed attempts. The right move is to ask the customer for a different payment method.
Is it safe to try?
It starts in dry-run mode: it records failures and logs every email and retry it would perform, but contacts neither Stripe nor your customers until you set DRY_RUN=false. Try it with a Stripe test-mode key first.
Where can I see how much I already lose?
Export your failed payments and run them through the Stripe Failed Payment Recovery Analyzer. If your webhook endpoint rejects Stripe's signature, the Webhook Signature Debugger shows why.
Where does my Stripe key live?
In the environment of the server you deploy in your own account. The author runs nothing in between and never sees your key or your customers.