Configure Stripe Billing for Your Next.js SaaS

Complete guide to setting up Stripe payments in Makerkit. Configure subscriptions, one-off payments, webhooks, and the Customer Portal for your Next.js Supabase application.

Stripe is the default billing provider in Makerkit. It offers the most flexibility with support for multiple line items, metered billing, and advanced subscription management.

Prerequisites

Before you start:

  1. Create a Stripe account
  2. Have your Stripe API keys ready (Dashboard → Developers → API keys)
  3. Install the Stripe CLI for local webhook testing

Step 1: Environment Variables

Add these variables to your .env.local file:

# Stripe API Keys
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
VariableDescriptionWhere to Find
STRIPE_SECRET_KEYServer-side API keyDashboard → Developers → API keys
STRIPE_WEBHOOK_SECRETWebhook signature verificationGenerated by Stripe CLI or Dashboard
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYClient-side key (safe to expose)Dashboard → Developers → API keys

Step 2: Configure Billing Provider

Ensure Stripe is set as your billing provider:

NEXT_PUBLIC_BILLING_PROVIDER=stripe

And in the database:

UPDATE public.config SET billing_provider = 'stripe';

Step 3: Create Products in Stripe

  1. Go to Stripe Dashboard → Products
  2. Click Add product
  3. Configure your product:
    • Name: "Pro Plan", "Starter Plan", etc.
    • Pricing: Add prices for monthly and yearly intervals
    • Price ID: Copy the price_xxx ID for your billing schema

Important: The Price ID (e.g., price_1NNwYHI1i3VnbZTqI2UzaHIe) must match the id field in your billing schema's line items.

Step 4: Set Up Local Webhooks with Stripe CLI

The Stripe CLI forwards webhook events from Stripe to your local development server.

First, log in to Stripe:

docker run --rm -it --name=stripe \
-v ~/.config/stripe:/root/.config/stripe \
stripe/stripe-cli:latest login

This opens a browser window to authenticate. Complete the login process.

Then start listening for webhooks:

pnpm run stripe:listen

Or manually:

docker run --rm -it --name=stripe \
-v ~/.config/stripe:/root/.config/stripe \
stripe/stripe-cli:latest listen \
--forward-to http://host.docker.internal:3000/api/billing/webhook

Using Stripe CLI Directly

If you prefer installing Stripe CLI globally:

# macOS
brew install stripe/stripe-cli/stripe
# Login
stripe login
# Listen for webhooks
stripe listen --forward-to localhost:3000/api/billing/webhook

Copy the Webhook Secret

When you start listening, the CLI displays a webhook signing secret:

> Ready! Your webhook signing secret is whsec_xxxxxxxxxxxxx

Copy this value and add it to your .env.local:

STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxx

Linux Troubleshooting

If webhooks aren't reaching your app on Linux, try adding --network=host:

docker run --rm -it --name=stripe \
-v ~/.config/stripe:/root/.config/stripe \
stripe/stripe-cli:latest listen \
--network=host \
--forward-to http://localhost:3000/api/billing/webhook

Step 5: Configure Customer Portal

The Stripe Customer Portal lets users manage their subscriptions, payment methods, and invoices.

  1. Go to Stripe Dashboard → Settings → Billing → Customer portal
  2. Configure these settings:

Payment methods:

  • Allow customers to update payment methods: ✅

Subscriptions:

  • Allow customers to switch plans: ✅
  • Choose products customers can switch between
  • Configure proration behavior

Cancellations:

  • Allow customers to cancel subscriptions: ✅
  • Configure cancellation behavior (immediate vs. end of period)

Invoices:

  • Allow customers to view invoice history: ✅

Step 6: Production Webhooks

When deploying to production, configure webhooks in the Stripe Dashboard:

  1. Go to Stripe Dashboard → Developers → Webhooks
  2. Click Add endpoint
  3. Enter your webhook URL: https://yourdomain.com/api/billing/webhook
  4. Select events to listen for:

Required events:

  • checkout.session.completed
  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted

For one-off payments (optional):

  • checkout.session.async_payment_failed
  • checkout.session.async_payment_succeeded
  1. Click Add endpoint
  2. Copy the signing secret and add it to your production environment variables

Free Trials Without Credit Card

Allow users to start a trial without entering payment information:

STRIPE_ENABLE_TRIAL_WITHOUT_CC=true

When enabled, users can start a subscription with a trial period and won't be charged until the trial ends. They'll need to add a payment method before the trial expires.

You must also set trialDays in your billing schema:

{
id: 'pro-monthly',
name: 'Pro Monthly',
paymentType: 'recurring',
interval: 'month',
trialDays: 14, // 14-day free trial
lineItems: [/* ... */],
}

Migrating Existing Subscriptions

If you're migrating to Makerkit with existing Stripe subscriptions, you need to add metadata to each subscription.

Makerkit expects this metadata on subscriptions:

{
"accountId": "uuid-of-the-account"
}

Option 1: Add metadata manually

Use the Stripe Dashboard or a migration script to add the accountId metadata to existing subscriptions.

Option 2: Modify the webhook handler

If you can't update metadata, modify the webhook handler to look up accounts by customer ID:

packages/billing/stripe/src/services/stripe-webhook-handler.service.ts

// Instead of:
const accountId = subscription.metadata.accountId as string;
// Query your database:
const { data: customer } = await supabase
.from('billing_customers')
.select('account_id')
.eq('customer_id', subscription.customer)
.single();
const accountId = customer?.account_id;

Common Issues

Webhooks not received

  1. Check the CLI is running: pnpm run stripe:listen should show "Ready!"
  2. Verify the secret: Copy the new webhook secret after each CLI restart
  3. Check the account: Ensure you're logged into the correct Stripe account
  4. Check the URL: The webhook endpoint is /api/billing/webhook

"No such price" error

The Price ID in your billing schema doesn't exist in Stripe. Verify:

  1. You're using test mode keys with test mode prices (or live with live)
  2. The Price ID is copied correctly from Stripe Dashboard

Subscription not appearing in database

  1. Check webhook logs in Stripe Dashboard → Developers → Webhooks
  2. Look for errors in your application logs
  3. Verify the accountId is correctly passed in checkout metadata

Customer Portal not loading

  1. Ensure the Customer Portal is configured in Stripe Dashboard
  2. Check that the customer has a valid subscription
  3. Verify the customerId is correct

Testing Checklist

Before going live:

  • [ ] Test subscription checkout with test card 4242 4242 4242 4242
  • [ ] Verify subscription appears in user's billing section
  • [ ] Test subscription upgrade/downgrade via Customer Portal
  • [ ] Test subscription cancellation
  • [ ] Verify webhook events are processed correctly
  • [ ] Test with failing card 4000 0000 0000 0002 to verify error handling
  • [ ] For trials: test trial expiration and conversion to paid