WritingStripe Subscription Billing Lifecycle: Trials, Renewals, and Cancellations — LinkIT
4 min readstripe, subscriptions, billing, saas, payments

Stripe Subscription Billing Lifecycle: Trials, Renewals, and Cancellations

Understand the full Stripe subscription billing lifecycle — trials, renewals, dunning, cancellations, and the webhook events needed to keep your database in sync.

Subscription billing continues after checkout through trials, renewals, payment failures, plan changes, and cancellations. Keep the application’s billing and access state aligned with Stripe events and reconciliation checks.

The Stripe Subscription Billing Lifecycle

Trial and activation

For a legacy trial_end free trial, the subscription starts in trialing status and Stripe creates a zero-amount invoice. When the trial ends, billing behavior depends on payment availability and the configured end behavior; the subscription can become past due, paused, or canceled rather than automatically becoming active.

Events to handle:

  • customer.subscription.trial_will_end — fires three days before trial expiry; use this to send the "your trial ends soon" email
  • customer.subscription.updated — fires when status changes from trialing to active
  • invoice.payment_succeeded — fires when the first real charge succeeds

Renewal cycle

For a monthly subscription, Stripe generates an invoice roughly one hour before the renewal date, then attempts to collect payment. If the charge succeeds, the subscription renews automatically.

Events to handle:

  • invoice.created — invoice is generated but not yet finalized; you can add line items here if needed
  • invoice.finalized — invoice is finalized and payment will be attempted shortly
  • invoice.payment_succeeded — payment collected; extend access period in your database
  • invoice.payment_failed — payment declined; begin dunning sequence

Failed payments and dunning

When a renewal charge fails, Stripe does not immediately cancel the subscription. Instead, it retries the payment on a configurable schedule (Smart Retries or a custom schedule you define). The subscription moves to past_due status.

This is where many teams have gaps. A subscription in past_due status is still "active" in Stripe's model — the customer has not been cancelled. Your product needs to decide whether to restrict access immediately on the first failure, after a grace period, or only at final cancellation.

Events to handle:

  • invoice.payment_failed — send dunning email; optionally restrict access
  • customer.subscription.updated — fires when status changes to past_due
  • invoice.payment_action_required — payment requires 3D Secure authentication; send the hosted invoice link to the customer

If all retries fail, Stripe can be configured to cancel the subscription automatically (set in your billing settings under Subscription and recovery).

Cancellation

When a subscription is cancelled — by the customer through the Customer Portal, by your code via API, or by Stripe after failed payment retries — the customer.subscription.deleted event fires.

At this point, you must revoke access. Common mistakes:

  • Checking status === "canceled" on customer.subscription.updated instead of listening to customer.subscription.deleted
  • Assuming cancellation is immediate when the customer has set cancel_at_period_end = true (the subscription stays active until the period ends)

The cancel_at_period_end field is available on the subscription object. If it is true, the customer has scheduled a cancellation but access should remain active until current_period_end.

Events to handle:

  • customer.subscription.updated with cancel_at_period_end = true — subscription marked for cancellation; show the customer a confirmation, optionally offer a save incentive
  • customer.subscription.deleted — access revoked; update your database to reflect cancelled status

Upgrades and downgrades

When a customer changes plans through the Customer Portal or via your API, Stripe handles proration automatically (or you can disable it). The subscription object updates with the new price and plan.

Events to handle:

  • customer.subscription.updated — the items array will reflect the new plan; update your database to grant the new feature set

Subscription pause

Stripe supports pausing subscriptions (available on certain account configurations). A paused subscription does not generate invoices. Events:

  • customer.subscription.updated with pause_collection set — subscription is paused
  • customer.subscription.updated with pause_collection cleared — subscription is resumed

Building a Reliable Subscription State Machine

Your database's representation of subscription status should be derived from Stripe events, not managed independently. A practical schema:

subscriptions table:
  stripe_subscription_id  TEXT UNIQUE NOT NULL
  stripe_customer_id      TEXT NOT NULL
  status                  TEXT NOT NULL  -- active, trialing, past_due, canceled
  current_period_end      TIMESTAMPTZ NOT NULL
  cancel_at_period_end    BOOLEAN NOT NULL DEFAULT false
  plan_id                 TEXT NOT NULL
  updated_at              TIMESTAMPTZ NOT NULL

Every webhook handler for subscription events updates this table. Your application checks status and current_period_end to determine access. This keeps your access logic simple: if status is active or trialing and current_period_end is in the future, the customer has access.

Testing the Full Lifecycle

The Stripe CLI lets you trigger any event in your local environment:

stripe trigger customer.subscription.deleted
stripe trigger invoice.payment_failed

Test subscription transitions and recovery paths before launch, including how cancellation changes access.

Discuss subscription billing implementation with LinkIT.