Recover failed Stripe payments

Stripe retries failed subscription invoices on its own and can email the customer. What it does not do is vary the channel per customer or let you set retry policy per decline code. RRLabs subscribes to the same events and orchestrates that layer while Stripe stays the source of truth for every charge.

The Stripe events recovery depends on

invoice.payment_failed
A subscription invoice could not be collected — this opens a recovery case.
invoice.paid
The invoice was collected. Close the case and attribute the recovery.
customer.subscription.deleted
Stripe gave up. The case closes as lost.

Verifying Stripe webhooks

Scheme
HMAC-SHA256 over `${timestamp}.${rawBody}`
Signature location
Stripe-Signature

Compare with a timing-safe equality check and reject timestamps older than five minutes to block replays.

import Stripe from "stripe";

const event = stripe.webhooks.constructEvent(
  rawBody,                       // the raw string, never the parsed JSON
  req.headers["stripe-signature"]!,
  process.env.STRIPE_WEBHOOK_SECRET!,
);

if (event.type === "invoice.payment_failed") {
  const invoice = event.data.object;
  await rrlabs.enqueueRecovery({
    provider: "stripe",
    externalId: invoice.id,
    amountCents: invoice.amount_due,
    currency: invoice.currency,
    declineCode: invoice.last_finalization_error?.decline_code ?? null,
  });
}

Decline playbook for Stripe

Recovery actions per Stripe failure signal
Failure signalRecovery action
insufficient_fundsRetry in 2–4 days, ideally near a common payday. Message tone: neutral reminder, no card update needed.
expired_cardDo not retry the same card. Send an update-payment link over email, then WhatsApp or SMS if unopened.
3ds_required / authentication_requiredRetries will keep failing. Send the Stripe-hosted authentication link immediately on the channel the customer reads fastest.
do_not_honorRetry once after a longer gap, then escalate to a customer message asking them to contact their bank.

What happens after a payment fails

  1. T+0s

    Payment failed

    Provider webhook received and verified

  2. T+2s

    AI analysis

    Decline reason classified, amount at risk scored

  3. T+5s

    Dynamic email

    Copy generated for that decline code and customer

  4. Day 2

    Smart retry

    Retry scheduled only when the decline code is retryable

  5. Day 2

    WhatsApp push

    Second channel used when the email went unanswered

  6. On success

    Recovered

    Counted only when the provider confirms the charge

Timings describe the configured workflow, not a guaranteed outcome. Every step is skipped when the decline reason makes it counter-productive.

Frequently asked questions

How does RRLabs intercept Stripe invoice.payment_failed?
Stripe posts the event to an RRLabs endpoint that verifies the Stripe-Signature header, records the failure with its decline code, and opens a recovery case. Nothing is charged or modified in Stripe.
Does this conflict with Stripe Smart Retries?
No. Smart Retries keeps running. RRLabs adds customer messaging and, where you enable it, its own retry scheduling for the decline codes you choose.

Connect Stripe with read-only credentials

RRLabs reads failure events and orchestrates recovery. It never holds card data and never becomes the merchant of record.

Get early access