Photo by PJH on Unsplash

On this page

This chapter follows Webhooks in Stripe.

Subscriptions connect a Customer to one or more recurring Prices. Stripe creates Invoices each cycle, attempts payment with the default payment method, and emits Events your app uses to grant or revoke access.

Checkout in subscription mode

The fastest path mirrors one-time Checkout—set mode: 'subscription' and pass recurring Price ids:

routes/subscribe.js
import { stripe } from '../stripe-client.js';
import { usersById } from '../store.js';
const BASE_URL = process.env.BASE_URL ?? 'http://localhost:4242';
export async function createSubscriptionCheckout(req, res) {
const { userId, priceId } = req.body;
const user = usersById.get(userId);
if (!user) return res.status(404).json({ error: 'Unknown user' });
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
customer: user.stripeCustomerId,
line_items: [{ price: priceId, quantity: 1 }],
success_url: `${BASE_URL}/success.html?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${BASE_URL}/cancel.html`,
client_reference_id: userId,
subscription_data: {
trial_period_days: 14,
metadata: { appUserId: userId },
},
});
res.json({ url: session.url });
}

After Checkout, retrieve session.subscription or listen for customer.subscription.created in your webhook handler.

Subscription lifecycle and statuses

Diagram 1
stateDiagram-v2
  [*] --> incomplete: create
  incomplete --> active: first payment succeeds
  incomplete --> incomplete_expired: timeout
  active --> past_due: payment fails
  past_due --> active: payment recovers
  past_due --> canceled: dunning exhausted
  active --> canceled: cancel at period end
  trialing --> active: trial ends with payment
  trialing --> canceled: cancel during trial
Status Access in your app
trialing Grant trial features until trial end
active Full paid access
past_due Grace period policy—warn, limit, or read-only
canceled Revoke at ended_at or immediately if you canceled now
unpaid Treat as lapsed

Store subscription.id and status on your user record; refresh from webhooks, not from the success page alone.

Invoices and recurring payment

Each billing period Stripe generates an Invoice and attempts automatic collection. Handle:

  • invoice.paid — Extend entitlement for the new period.
  • invoice.payment_failed — Notify the customer; Stripe retries based on your Dashboard smart retries settings.
function onInvoicePaid(invoice) {
const subscriptionId = invoice.subscription;
console.log('Subscription paid', subscriptionId, invoice.period_end);
}

Upgrades, downgrades, and proration

Change items on an existing subscription:

const subscription = await stripe.subscriptions.retrieve('sub_...');
await stripe.subscriptions.update(subscription.id, {
items: [
{ id: subscription.items.data[0].id, price: 'price_new_plan' },
],
proration_behavior: 'create_prorations',
});

Proration credits unused time and charges the new plan proportionally. Choose none when swapping at period boundaries only. Expose plan changes through your UI or the Customer Portal.

Cancellation

Cancel immediately or at period end:

await stripe.subscriptions.update('sub_...', {
cancel_at_period_end: true,
});

Webhook customer.subscription.updated reflects cancel_at_period_end; customer.subscription.deleted fires when the subscription ends.

Customer Portal

Enable the Customer Portal in Dashboard settings so customers update payment methods, view invoices, and cancel without custom pages. Create a portal session on the server:

const portal = await stripe.billingPortal.sessions.create({
customer: user.stripeCustomerId,
return_url: `${BASE_URL}/account.html`,
});
res.json({ url: portal.url });

Portal configuration controls which Prices customers can switch to—keep it aligned with your public catalog.

Next: Stripe beyond payments.

Check your understanding

Question 1 of 6

0 of 6 checked

  1. 1. Which Checkout mode creates a Subscription from recurring Prices?

Search articles

Type a word to search published articles.

Open the search page