Stripe Setup

Connect Stripe to your Next.js Prisma application for subscription billing

Connecting Stripe involves four steps: obtain API keys from your Stripe Dashboard, create products with Price IDs, set up webhooks for subscription sync, and verify with test cards.

This page is part of the Billing & Subscriptions documentation.

The Stripe integration leverages Better Auth's Stripe plugin for checkout sessions, customer records, subscription lifecycle, and webhook processing, all accessible through the unified BillingClient API.

Before You Start

  • A Stripe account (stripe.com)
  • Access to your .env file

Use Stripe test mode keys during development. Production verification can wait until launch.

Step 1: Obtain API Keys

Development Keys (Test Mode)

  1. Sign in to Stripe Dashboard
  2. Switch to Test mode or use the Sandbox
  3. Copy the Secret key (prefix: sk_test_)
Stripe Sandbox

Add to your .env.local file:

STRIPE_SECRET_KEY=sk_test_51O...your_test_key_here

This key is for testing only.

Step 2: Set Up Products and Prices

Via Stripe Dashboard

Stripe Add Product

Create products and prices directly in the Stripe Dashboard. We'll set up Starter and Pro tiers.

Both products need monthly and yearly prices. Starter at $9.99/$99.99, Pro at $19.99/$199.99.

Steps:

  1. Navigate to ProductsAdd product
  2. Create a product for each tier (e.g., "Starter", "Pro", "Enterprise")
  3. For each product, add prices:
    • Set billing period (monthly/yearly)
    • Set amount
    • Enable "Recurring" billing
  4. Copy each Price ID (starts with price_)

Sample Product Configuration

Example pricing for Starter and Pro:

Starter Product

  • Monthly Price ($9.99/month)
  • Yearly Price ($99.99/year)

Pro Product

  • Monthly Price ($19.99/month)
  • Yearly Price ($199.99/year)
Stripe Products

Store Price IDs

After creating products, copy each Price ID.

Add them to .env.development or .env.local:

STRIPE_PRICE_STARTER_MONTHLY=price_...
STRIPE_PRICE_STARTER_YEARLY=price_...
STRIPE_PRICE_PRO_MONTHLY=price_...
STRIPE_PRICE_PRO_YEARLY=price_...

Note: Variable names are flexible. What matters is using the correct Price ID (prefix price_), not Product ID (prefix prod_). This is a frequent configuration error.

Step 3: Map Plans in Config

Update your billing configuration with product details:

export const billingConfig: BillingConfig = {
products: [
{
id: 'starter',
name: 'Starter',
description: 'For individuals and small teams',
currency: 'USD',
features: ['Core features', 'Email support'],
plans: [
{
name: 'starter-monthly',
planId: process.env.STRIPE_PRICE_STARTER_MONTHLY!,
displayName: 'Starter Monthly',
interval: 'month',
cost: 9.99,
},
{
name: 'starter-yearly',
planId: process.env.STRIPE_PRICE_STARTER_YEARLY!,
displayName: 'Starter Yearly',
interval: 'year',
cost: 99.99,
},
],
},
],
};

Enable Plan Switching in Portal

Stripe's Customer Portal can allow plan changes. Configure this in the Stripe Dashboard.

See Stripe Billing Portal docs for details.

Organization Billing (B2B)

With Stripe, Makerkit supports dedicated organization billing:

  • Users have users.stripeCustomerId (individual billing)
  • Organizations have organizations.stripeCustomerId (team billing)

Better Auth's Stripe plugin handles customer creation automatically:

  • Org Stripe customers are created during first checkout/upgrade
  • Organization name updates sync to Stripe's customer record
  • Orgs with active subscriptions can't be deleted

Customize customer creation in packages/billing/stripe/src/stripe-plugin.ts.

Step 4: Set Up Webhooks

Webhooks enable real-time subscription event handling from Stripe.

Local Development

A Docker command starts the Stripe CLI and forwards webhooks locally.

Run:

pnpm --filter web run stripe:listen

Without Docker: Install Stripe CLI globally and run:

stripe listen --forward-to http://localhost:3000/api/auth/stripe/webhook

First run prompts for Stripe login. Follow the on-screen instructions.

Stripe CLI Login

After logging in, run again:

pnpm --filter web run stripe:listen

Copy the webhook signing secret (prefix whsec_) from the output:

STRIPE_WEBHOOK_SECRET=whsec_...

This secret is required for both development and production. Without it, signature verification fails and events won't process.

For remote server testing, configure the webhook destination URL in Stripe Sandbox, similar to production setup.

Production Webhooks

For production, configure the webhook endpoint in Stripe Dashboard:

  1. In Stripe Dashboard, navigate to DevelopersWebhooks
  2. Click Add endpoint
  3. Set endpoint URL: https://yourdomain.com/api/auth/stripe/webhook
  4. Select events to listen for at least the following:
    • checkout.session.completed
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
  5. More webhooks events may be required depending on your needs (eg. if you need to handle othr events)
  6. Copy the webhook signing secret
  7. Add to production environment variables (always prefer using the hosting provider's environment variables editor)
STRIPE_WEBHOOK_SECRET=whsec_...production_secret

Step 5: Verify Integration

Test Checkout Flow

Stripe provides test cards:

Card NumberDescription
4242 4242 4242 4242Successful payment
4000 0025 0000 3155Requires 3D Secure authentication
4000 0000 0000 9995Payment declined

Verify Trial Functionality

  1. Start a subscription with trial enabled
  2. Confirm trial end date in Stripe Dashboard
  3. Verify status: 'trialing' shows in your app

Environment Variables

Full Stripe configuration:

# Stripe API Keys
STRIPE_SECRET_KEY=sk_test_... # or sk_live_... for production
# Webhook Secret
STRIPE_WEBHOOK_SECRET=whsec_...
# Optional Stripe behavior toggles (defaults shown)
CREATE_CUSTOMER_ON_SIGN_UP=true
STRIPE_ENABLE_TRIAL_WITHOUT_CC=false
ENABLE_AUTOMATIC_TAX_CALCULATION=false
ENABLE_TAX_ID_COLLECTION=false
# Price IDs (from Stripe Dashboard) (names are up to you)
STRIPE_PRICE_STARTER_MONTHLY=price_...
STRIPE_PRICE_STARTER_YEARLY=price_...
STRIPE_PRICE_PRO_MONTHLY=price_...
STRIPE_PRICE_PRO_YEARLY=price_...
STRIPE_PRICE_ENTERPRISE_MONTHLY=price_...
STRIPE_PRICE_ENTERPRISE_YEARLY=price_...
# Application URL (for redirects)
NEXT_PUBLIC_SITE_URL=http://localhost:3000 # or your production domain

Security Guidelines

API Key Safety

  • Keep API keys out of version control
  • Separate test and production keys
  • Rotate keys regularly
  • Limit permissions in Stripe Dashboard

Webhook Verification

The plugin verifies webhook signatures automatically:

stripe({
stripeClient,
stripeWebhookSecret: STRIPE_WEBHOOK_SECRET,
// Webhooks are verified before processing
});

Launch Checklist

Before going live:

  • [ ] Swap test keys for live keys
  • [ ] Set up production webhook endpoint
  • [ ] Verify webhook events in production
  • [ ] Enable billing email notifications
  • [ ] Configure Customer Portal in Stripe Dashboard
  • [ ] Review Stripe Radar fraud rules

Watch Out For

  • Product ID instead of Price ID: Billing config needs Price IDs (price_...), not Product IDs (prod_...). Most common mistake.
  • Webhook secret mismatch: Development and production secrets differ. CLI-generated secrets won't work in production.
  • Skipping stripe:listen locally: Without the forwarder, subscription state doesn't sync after checkout.
  • Wrong webhook URL: The endpoint is /api/auth/stripe/webhook, not /api/stripe/webhook or /webhooks/stripe.
  • Missing webhook events: At minimum: checkout.session.completed, customer.subscription.created, customer.subscription.updated, customer.subscription.deleted.
  • Test keys in production: sk_test_... keys fail in production. Switch to sk_live_... before launch.

Frequently Asked Questions

Where do I find my webhook signing secret?
For local dev, run pnpm --filter web run stripe:listen and copy the whsec_... output. For production, go to Stripe Dashboard → Developers → Webhooks → your endpoint → Signing secret.
Why aren't subscriptions syncing after checkout?
Webhooks aren't reaching your app. Check that stripe:listen is running locally, or verify your production webhook URL is correct and the secret matches.
Can I use the same products for test and production?
No. Test mode and live mode have separate product catalogs. You'll need to recreate products and prices in live mode with new Price IDs.
How do I enable plan switching in the customer portal?
In Stripe Dashboard → Settings → Billing → Customer portal → Configure. Enable 'Switch plans' under Subscriptions.
What's the difference between test mode and sandbox?
Stripe Sandbox is newer and provides isolated test environments. Test mode is the classic approach. Both work with sk_test_ keys.

Next: Polar Setup →