Skip to content

Billing Quickstart Fresh

Build a prebuilt subscription page using Stripe Checkout and Stripe Billing.

Overview

The recommended pattern:

  1. Customer clicks checkout on your pricing page
  2. Redirected to Stripe-hosted Checkout
  3. Stripe creates the customer, subscription, and handles payment
  4. Customer redirected to your success page
  5. Webhook fires — you provision access

Step 1: Create products and prices

Create your subscription product in Dashboard → Product catalog → Create product. Set the pricing model (flat rate, per-seat, etc.) and billing interval.

Or via API:

bash
curl https://api.stripe.com/v1/products \
  -u "sk_test_YOUR_KEY:" \
  -d name="Pro Plan"

curl https://api.stripe.com/v1/prices \
  -u "sk_test_YOUR_KEY:" \
  -d product=prod_123 \
  -d unit_amount=2000 \
  -d currency=usd \
  -d "recurring[interval]=month" \
  -d lookup_key=pro_monthly

Step 2: Create a Checkout Session

javascript
const stripe = require('stripe')('sk_test_YOUR_KEY');
const express = require('express');
const app = express();

app.post('/create-checkout-session', async (req, res) => {
  const prices = await stripe.prices.list({
    lookup_keys: [req.body.lookup_key],
    expand: ['data.product'],
  });

  const session = await stripe.checkout.sessions.create({
    billing_address_collection: 'auto',
    line_items: [{ price: prices.data[0].id, quantity: 1 }],
    mode: 'subscription',
    success_url: `${YOUR_DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${YOUR_DOMAIN}/cancel`,
  });

  res.redirect(303, session.url);
});

Step 3: Handle webhook events

Register a webhook endpoint in Dashboard → Workbench → Webhooks.

javascript
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const endpointSecret = 'whsec_YOUR_SECRET';
  const sig = req.headers['stripe-signature'];

  let event;
  try {
    event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
  } catch (err) {
    return res.sendStatus(400);
  }

  switch (event.type) {
    case 'customer.subscription.created':
      // Provision access for new subscriber
      break;
    case 'customer.subscription.updated':
      // Handle plan changes
      break;
    case 'customer.subscription.deleted':
      // Revoke access
      break;
    case 'customer.subscription.trial_will_end':
      // Send reminder, collect payment method if missing
      break;
    case 'entitlements.active_entitlement_summary.updated':
      // Update feature access based on new entitlements
      break;
  }

  res.send();
});

Step 4: Customer portal

Let customers manage their subscriptions without contacting support.

javascript
app.post('/create-portal-session', async (req, res) => {
  const checkoutSession = await stripe.checkout.sessions.retrieve(req.body.session_id);
  const returnUrl = YOUR_DOMAIN;

  const portalSession = await stripe.billingPortal.sessions.create({
    customer: checkoutSession.customer,
    return_url: returnUrl,
  });

  res.redirect(303, portalSession.url);
});

Test cards

ScenarioCard number
Payment succeeds4242424242424242
Requires 3DS4000002500003155
Payment declined4000000000009995

Optional features

Add a free trial

javascript
const session = await stripe.checkout.sessions.create({
  // ...
  subscription_data: { trial_period_days: 14 },
});

If you start a trial without collecting a payment method, set trial_settings[end_behavior][missing_payment_method] to pause or cancel.

Apply a coupon

javascript
const session = await stripe.checkout.sessions.create({
  // ...
  discounts: [{ coupon: 'COUPON_ID' }],
});

Set billing cycle anchor

javascript
const session = await stripe.checkout.sessions.create({
  // ...
  subscription_data: { billing_cycle_anchor: 1672531200 },
});

Automatic tax

javascript
const session = await stripe.checkout.sessions.create({
  // ...
  automatic_tax: { enabled: true },
});

Stripe API Reference - Self-contained docs reference, refreshed 2026-05-18