When a Stripe subscription payment fails at renewal, Stripe Billing marks the invoice as unpaid, moves the subscription to past_due, and, if you have turned them on, retries the charge and emails the customer on the schedule set in your Dashboard. When the last retry fails, Stripe applies the final action you chose: cancel the subscription, mark it unpaid, leave it past due, or pause it where eligible. What Stripe does not decide is what the customer can still use while that happens. That access policy, and keeping it in step with billing state, belongs to your application.

This guide is written for the engineers and product owners who own that gap. It covers why renewals fail, which parts of recovery Stripe automates, how to map each subscription status to product access, how to configure retries and dunning emails, how to handle renewals that need customer authentication, which webhook events to listen for, and how to test all of it before a real customer's card is declined. It ends with a checklist you can work through against your own integration.

Stripe subscription failed payment: what actually happens

A subscription renewal is an invoice. At the end of each billing period Stripe creates the renewal invoice, finalizes it and, for subscriptions with collection_method set to charge_automatically, attempts to charge the saved payment method. If the charge succeeds, the invoice is paid and you receive invoice.paid. If it fails, you receive invoice.payment_failed, the invoice stays open, and the subscription moves to past_due.

From there, three things run in parallel. Stripe schedules further attempts according to your retry settings. Stripe may email the customer, depending on which customer emails you enabled. And your application receives a stream of events that it has to turn into a decision about access. If the customer updates their card or completes authentication and a later attempt succeeds, the invoice is paid and the subscription returns to active. If every attempt fails, the subscription moves to whatever end state your settings specify.

A failed renewal, from first decline to recovery or the final action.

The first invoice of a new subscription behaves differently from a renewal. If the first payment fails, the subscription is incomplete, and the customer has 23 hours to complete payment before it becomes incomplete_expired and the invoice is voided. That case is a checkout problem, handled while the customer is usually still in your sign-up flow. Everything else in this guide is about renewals, where the customer is not present and the payment is attempted off-session.

Why do subscription renewals fail?

Renewals fail for reasons that fall into a few groups, and the group matters because it decides whether a retry can possibly work.

  • Soft declines. The issuer declined this attempt but the card is still valid: insufficient funds, a temporary issuer problem, or a generic decline. These are the failures retries exist for. A later attempt, often on a different day, can succeed without the customer doing anything.
  • Hard declines. The issuer has said the card should not be charged again as it stands: the card was reported lost or stolen, the number is wrong, or the authorization was revoked. Stripe publishes a list of hard decline codes for which it will not execute an automatic retry until a new payment method appears. Only the customer can fix these.
  • Expired or replaced cards. A card on file reaches its expiry date or is reissued. Depending on the card network and issuer, updated details can sometimes reach Stripe automatically; when they do not, the renewal declines and the customer needs to add a new card.
  • Authentication required. Some issuers, and some regulatory regimes such as Strong Customer Authentication in Europe, require the cardholder to authenticate a payment. For an off-session renewal, that means the payment cannot complete until the customer comes back and completes a 3D Secure challenge. Stripe sends invoice.payment_action_required for this case, and authentication_required is on Stripe's hard-decline list, so a blind retry will not fix it.
  • No usable payment method. The customer has no default payment method on the subscription or the customer record, often after a migration or a portal change that went wrong. Stripe does not retry when no payment method is available.

Stripe also documents a few situations where it does not retry at all: India-issued cards under that country's recurring-payment rules, and payments on a Connect account that has been disconnected. Local payment methods such as ACH Direct Debit or SEPA Direct Debit have their own retry rules, and retries for them are off by default. If you sell outside cards, check the retry documentation for each method you accept.

The practical lesson is that the decline code belongs in your logs and in your customer messaging. A soft decline justifies patience. A hard decline or an authentication requirement means the customer must act, so the sooner they hear about it in plain language, the better your chance of keeping them.

What Stripe does automatically, and what your application must do

Most failed-payment bugs we see come from an unclear split of responsibilities. Teams assume Stripe will lock the customer out, or assume their app is told about every change in a form it already understands. Neither is true by default. Here is the split.

  • Stripe handles: creating and finalizing renewal invoices; attempting the charge; retrying on the schedule you configured; sending the customer emails you enabled; moving the subscription between past_due, active, unpaid, canceled and paused according to your settings; hosting the invoice page and the customer portal; and emitting webhook events for each change.
  • Your application handles: deciding what each status means for product access; recording billing state against your own account or workspace record; showing in-app banners and messages; sending any communication Stripe does not send, such as in-app notices or messages to an account owner who is not the billing contact; giving the customer a working path to update their payment method or authenticate; and correcting drift when events are missed or arrive out of order.

Stripe can also hold product entitlements for you. When a subscription becomes active, Stripe Billing can create active entitlements for the features attached to the product, and it sends entitlements.active_entitlement_summary.updated when they change. That reduces mapping code, but it does not remove the policy question: you still decide whether a past_due customer keeps features during a grace period, and entitlements follow Stripe's view of the subscription, not your business rules about grace.

Subscription statuses and what each should mean for access

A Stripe subscription has one of eight statuses. Each one carries a precise meaning in Stripe's subscription lifecycle documentation, and each needs an explicit decision in your product.

  • trialing means the subscription is in a trial and you can safely provision access. It moves to active when the first payment succeeds.
  • active means the subscription is in good standing. Note that active does not always mean every past invoice is paid; under Stripe's default status resolution, only the most recent non-voided invoice decides the status.
  • incomplete means the first payment has not succeeded yet, either because it failed, needs authentication, or is still processing. Do not grant paid access on incomplete.
  • incomplete_expired means the first payment never succeeded within 23 hours. The subscription will not bill. Treat it as a sign-up that did not convert.
  • past_due means payment on the latest finalized invoice failed or was not attempted. Stripe may still retry, but the status itself does not guarantee another attempt. This is where your grace policy lives.
  • unpaid means retries ended without success and your settings chose to mark the subscription unpaid rather than cancel it. Invoices continue to be generated as drafts but are not charged. Stripe's own guidance is to revoke access in this state.
  • paused means no new invoices are being created. A subscription can pause when a trial ends without a payment method, or after a payment failure if you configured pausing and the subscription is eligible.
  • canceled is terminal. The subscription cannot be reactivated; a returning customer needs a new subscription.
Subscription status versus access policy. The right-hand column is a starting point for your own policy, not a rule Stripe enforces.

One account-level setting changes how these statuses resolve. In your subscription settings, Stripe lets you choose whether status depends on the most recent invoice (the default) or on all outstanding invoices. With all invoices, a subscription cannot return to active while any applicable earlier invoice is still open. If your finance team expects every invoice to be settled before full service resumes, choose that setting deliberately and make your access code expect it.

Designing an access policy: grace, limited mode, pause or cancel

An access policy is the rule that turns billing state into product behavior. It should be written down before anyone touches code, agreed by product, support and finance, and expressed in one place in the codebase. There are four common shapes, and most products combine two or three of them.

Grace period

The customer keeps full access for a fixed number of days after the first failure, with a clear in-app banner and a link to fix payment. Grace periods suit B2B products where cutting off a team mid-week creates more damage than a few days of unpaid usage. Measure the grace period from the first failed attempt, store that timestamp yourself, and do not rely on reading it back from the retry schedule, which you may change later.

Limited or read-only mode

After the grace period, the customer can still sign in, see their data and export it, but cannot create new work, invite users or use metered features. Limited mode keeps the customer close to the fix-payment button while stopping cost growth. It is usually the best middle state for products that hold customer data.

Pause

Stripe can pause eligible subscriptions after the first renewal failure or after retries are exhausted, so that no further invoices are created until the subscription resumes. Pausing on payment failure requires subscriptions on Stripe's flexible billing mode, and the subscription can resume automatically when the invoice that caused the pause is paid, or stay paused until you resume it. The pause-on-failure guide lists the eligibility rules; if Stripe cannot pause a subscription after the final attempt, it cancels it instead, so test which path your subscriptions take.

Cancel

Cancelling is the cleanest end state for accounting and the harshest for the customer. To make Stripe cancel a subscription after failed payments, set the final retry action to cancel the subscription; it then becomes canceled once the retry schedule ends. You can also cancel through the API from your own logic, for example at the end of your own grace period. Remember what cancellation does: it stops new invoices, turns off automatic collection on outstanding invoices from that subscription, and is terminal. If you expect customers to come back, a returning customer flow that creates a fresh subscription has to exist.

A sensible default for many SaaS products is: full access with a banner while past_due for a short grace period, limited mode for the rest of the retry window, then either unpaid with access revoked or a cancel at the end. Whatever you pick, write the rule as a table of status and age against access level, and give support a way to see why an account is in the state it is in.

How to retry a failed subscription payment in Stripe

Stripe configures subscription retries under Billing, then Revenue recovery, then Retries in the Dashboard. You choose between two approaches.

  • Smart Retries. Stripe chooses when to retry, using time-dependent signals to pick times when a payment is more likely to succeed. You set how many attempts and over what maximum window: one week, two weeks, three weeks, one month or two months. Stripe's documented recommended default is 8 tries within 2 weeks. Stripe Billing automations can apply different retry policies to different customer segments.
  • A custom retry schedule. You turn Smart Retries off and define up to three retries, each a fixed number of days after the previous attempt. Custom schedules are predictable, which helps when finance or support needs to know exactly when the next attempt will happen, at the cost of not adapting timing to each payment.

Below the retry choice, the same settings page asks what should happen to the subscription if all retries fail: cancel it, mark it unpaid, leave it past due, or pause it if eligible. This is the setting most teams mean when they search for how to cancel a Stripe subscription after a failed payment. The choice should match your access policy. If your app revokes access on unpaid but your account is set to leave subscriptions past due, customers will sit in grace indefinitely.

Illustrative only: one way a two-week retry window can line up with your own grace and limited-mode periods. Smart Retries chooses the actual attempt times.

Two details catch teams out. First, the invoice field next_payment_attempt tells you when Stripe plans to try again, and attempt_count on the invoice tells you how many attempts have been made. For accounts using Billing automations, Stripe documents that next_payment_attempt is set on invoice.updated rather than on invoice.payment_failed. Second, for hard declines, scheduled retries still tick over and attempt_count still increments, but no charge is executed until a new payment method is detected. A rising attempt count is not proof that the card was actually tried.

There is also a payment method precedence rule that explains many missed recoveries. When retrying, Stripe uses the subscription's default payment method first, then the subscription's default source, then the customer's invoice default payment method, then the legacy customer default source. If a subscription has its own default_payment_method and your update-card flow only changes the customer's invoice_settings.default_payment_method, Stripe keeps retrying the old card on the subscription. Update the field where the failure happened, or clear the subscription-level default so the customer default applies.

Stripe dunning emails and customer communication

People search for Stripe smart dunning, but Stripe does not use that name. What Stripe calls dunning is spread across Revenue recovery: Smart Retries or custom retries for the timing, and customer emails for the messaging. The emails are switched on individually in the Dashboard, and the customer emails guide describes each one. The ones that matter for failed renewals are:

  • Failed card payment emails, sent after each failed payment, telling the customer their subscription payment failed and giving them a way to update their payment method.
  • Payment confirmation emails, which send a Stripe-hosted link when a payment needs the customer to confirm it, for example with 3D Secure, with optional reminders until it is confirmed or expires.
  • Expiring card emails, sent about a month before the default card on file expires, which prevent some failures from happening at all.
  • Upcoming renewal reminders for charge_automatically subscriptions, sent a configurable number of days before renewal, and trial ending reminders.

Each of those emails links to a page where the customer can update their payment method. You can point that link at a Stripe-hosted page or at your own subscription management page. The Stripe-hosted link stops working once the subscription becomes canceled, incomplete_expired or unpaid, or once the relevant renewal period has passed. If your final action is mark unpaid, make sure the customer still has a working way to pay: a signed-in billing page in your app, the invoice's own hosted payment page, or a fresh customer portal session.

The customer portal is the quickest robust option for self-service. Your server creates a portal session for the customer with a return_url, and the customer can update payment methods, pay and download invoices, and cancel or change plans, depending on your configuration. For a failed payment, a portal flow with type payment_method_update deep-links straight to adding a new card. Stripe documents that this flow sets the new card as the customer's invoice default payment method, which brings back the precedence rule above: if your subscriptions carry their own default payment method, update or clear it as well.

Stripe's emails go to the billing contact on the customer record. In B2B products that is often not the person who uses the product every day, so your in-app banner carries more weight than any email. Keep the wording factual: what failed, what the customer needs to do, what happens next and when. Avoid threatening language in the first message; most first failures are soft declines that resolve on their own.

Handling payment_action_required and SCA on renewals

When an issuer requires the cardholder to authenticate an off-session renewal, the payment cannot complete without the customer. Stripe signals this with the invoice.payment_action_required event, and the PaymentIntent behind the invoice moves to requires_action. Whether authentication is requested depends on the issuer and on your Radar rules, so it can appear even for customers outside regions with formal authentication mandates.

You have two ways to get the customer through it. The low-code route is to enable the Stripe-hosted confirmation link email, so Stripe emails the customer a link to a page where they complete authentication, with optional reminders. The integrated route is to show the customer a prompt in your app: your server retrieves the invoice's PaymentIntent client secret, and your frontend passes it to stripe.handleNextAction from Stripe.js, which walks the customer through the challenge and returns the result. Either way, do not provision or restore access from the browser result. Wait for invoice.paid and confirm the subscription is active before you change access.

Because authentication_required is on the list of codes Stripe will not blindly retry, these subscriptions will sit in past_due until the customer acts. That makes them the most valuable cases for in-app messaging: the customer is often still using the product and only needs a few seconds to approve the payment.

The webhook events involved

Failed-payment handling depends on a small set of events. Our Stripe subscription webhook events guide covers the full lifecycle; for failures, these are the ones to handle.

  • invoice.payment_failed: a payment attempt on an invoice failed. Read attempt_count, the decline information on the related payment, and next_payment_attempt. Record the first failure time for your grace period and trigger in-app messaging.
  • invoice.payment_action_required: the invoice needs customer authentication. Prompt the customer in-app and, if you rely on Stripe's emails, make sure the confirmation email is enabled.
  • invoice.paid: the invoice was paid, including after a retry or after the customer fixed their payment method. This is your recovery signal.
  • customer.subscription.updated: the subscription changed, including status transitions such as active to past_due and past_due to active or unpaid. Use it to keep your stored status current.
  • customer.subscription.deleted: the subscription ended, whether through cancellation by the customer, by you, or by the final retry action.
  • customer.subscription.paused and customer.subscription.resumed: the subscription entered or left the paused status.
  • invoice.upcoming: sent a configurable number of days before a renewal. Useful for checking that a payment method exists and is not about to expire.

The handler pattern matters as much as the event list. Verify signatures, record event IDs so duplicate deliveries are ignored, acknowledge quickly and process asynchronously, and never assume events arrive in order. Our article on Stripe webhook best practices, idempotency and retries goes through each of those in detail. The short version for billing is that the event tells you something changed, and the current Stripe object tells you what is true now.

Mapping subscription status to access from webhook events (Node.js)
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const ACCESS_BY_STATUS = {
  trialing: "full",
  active: "full",
  past_due: "grace", // grace first; a scheduled job downgrades later
  unpaid: "none",
  paused: "read_only",
  incomplete: "none",
  incomplete_expired: "none",
  canceled: "none",
};
export async function syncAccess(event, db) {
  if (await db.processedEvents.has(event.id)) return; // duplicate delivery
  const obj = event.data.object;
  const subId = obj.object === "invoice"
    ? obj.parent?.subscription_details?.subscription ?? obj.subscription
    : obj.id;
  if (!subId) return;
  // Re-read the subscription so an out-of-order event cannot roll state back.
  const sub = await stripe.subscriptions.retrieve(subId);
  await db.accounts.updateByStripeCustomer(sub.customer, {
    billingStatus: sub.status,
    access: ACCESS_BY_STATUS[sub.status] ?? "none",
    billingSyncedAt: new Date(),
  });
  await db.processedEvents.add(event.id);
}

The sketch above re-reads the subscription rather than trusting the event payload, so a delayed customer.subscription.updated cannot overwrite a newer recovery. The past_due branch only sets grace; a scheduled job that compares the stored first-failure time with your grace length moves the account to limited mode. Keep that timing logic in your own code so a change to Stripe's retry settings never silently changes your access rules.

Reconciling access with billing state

Even with careful webhook handling, access will drift from billing state at some point. An endpoint returns errors during a deploy and events go undelivered; a support agent changes a subscription by hand in the Dashboard; a bug maps a status wrongly for a week. Webhooks are the fast path. A reconciler is the safety net.

A reconciler is a scheduled job that lists subscriptions from Stripe, compares each one's status with the access level stored in your database, and corrects or reports differences. For most products, running it every hour or every day is enough. Useful checks include:

  • Accounts with full access whose Stripe subscription is unpaid, canceled or incomplete_expired.
  • Accounts in limited mode whose subscription is active again, which means a recovery event was missed.
  • Accounts past their grace deadline that are still at full access.
  • Subscriptions in past_due whose invoice shows no future payment attempt and no recent customer contact, which are candidates for a manual outreach.
  • Customers whose subscription-level default payment method differs from the customer default, which explains retries hitting an old card.

Log every correction with the before and after state. A rising correction count is an early warning that your webhook path is losing events, long before customers start emailing support.

Testing failure scenarios in test mode

Failed-payment logic is rarely exercised in development because real cards almost always succeed. Stripe's testing tools make it practical to run every path before launch. Use a sandbox, and keep separate ones for local development and CI so automated tests do not disturb each other.

  • A renewal that fails. Stripe's Billing testing guide recommends attaching the test card 4000 0000 0000 0341 as the customer's default payment method and starting the subscription with a short trial. The card attaches successfully but declines when charged, so the first real charge at trial end fails. Without a test clock, Stripe notes it takes about an hour for the draft invoice to open and be attempted.
  • Renewals over weeks and months. Test clocks let you create customers and subscriptions inside a simulation and advance time, so you can watch a monthly or annual renewal fail, retries happen and the final action run, without waiting. Advance the clock in steps and assert your app's access level after each step.
  • Authentication on renewal. The test card 4000 0027 6000 3184 triggers 3D Secure on subscriptions and invoices. Use it to check that invoice.payment_action_required reaches your app, that your prompt or Stripe's hosted link works, and that access only changes after invoice.paid.
  • Recovery. After a failure, update the payment method to a succeeding test card through your own flow or the portal, then advance the clock and confirm the invoice is paid and access returns exactly once.
  • Duplicate and out-of-order events. Replay the same event twice and deliver an older customer.subscription.updated after a newer one. Your stored state should not change on the duplicate or roll back on the stale event.
  • Emails. Stripe can send customer emails from a sandbox when the test customer's address is the confirmed email of a team member with access to that sandbox.

The Stripe CLI can trigger individual events quickly, which is useful for checking that a handler runs. Stripe points out that triggered events carry placeholder data that does not match real subscriptions, so for anything involving state, create real test subscriptions and let the events come from them.

From here, this article is about Cevrynt

How Cevrynt approaches Stripe Billing failure handling

Cevrynt is a Stripe partner, a member of the Stripe Partner Ecosystem, and our engineers have built and repaired subscription billing for SaaS products. Arindam Pradhan, our founder, holds the Stripe Certified Professional, Implementation Architect certification. On a failed-payment engagement we start from your event log, account settings and access code, then write the access policy table with your team before changing anything in production.

The work happens in your own repository and Stripe account: verified, idempotent webhook handlers, a reconciler, the test-clock scenarios above, and a handover your team can maintain. See our Stripe Billing service or the wider Stripe services hub. If you are moving billing from another system at the same time, our guide to migrating subscriptions to Stripe Billing covers the cutover. Every Stripe project is scoped and priced in writing after a technical review; to start one, send us the details.