Stripe Setup

Configure your Stripe account, API keys, and webhooks

Set up Stripe in four steps: get your API keys from the Stripe Dashboard, create products with Price IDs, configure webhooks for subscription syncing, and test with Stripe's sandbox cards.

This guide is part of the Billing & Subscriptions documentation.

Stripe integration uses Better Auth's Stripe plugin to handle checkout sessions, customer management, subscription lifecycle, and webhook events - exposing these through MakerKit's unified BillingClient API.

Prerequisites

  • A Stripe account (sign up at stripe.com)
  • Access to your project's .env file

You can use Stripe's test mode keys for development. The verification process for production can wait until you're ready to launch.

Step 1: Get Your API Keys

Test Mode Keys (Development)

  1. Log in to your Stripe Dashboard
  2. Ensure you're in Test mode or you can use the newer Sandbox
  3. Copy your Secret key (starts with sk_test_)
Stripe Sandbox

Add to your .env.local file:

STRIPE_SECRET_KEY=sk_test_51O...your_test_key_here

This is a testing key and won't be used in production.

Step 2: Create Products and Prices

Using Stripe Dashboard

Stripe Add Product

To create products and prices, you can use the Stripe Dashboard. Let's walk you through creating a Starter and Pro product.

For both products, you can create a monthly and yearly price. For the Starter product, you can create a $9.99 and $99.99 price. For the Pro product, you can create a $19.99 and $199.99 price.

Here's going to be the 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_)

Example Product Setup

Below are some example product setups for a Starter and Pro product:

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

Configure Price IDs

Once created the products (Starter and Pro), you can copy the Price IDs.

Add your Price IDs to .env.development or .env.local (as these are used exclusively for development):

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

Note: the names of the environment variables are up to you. The key is that planId must be the correct identifier for the active provider. Please make sure you're using the Price ID and not the Product ID (can easily be identified by the prefix price_ and prod_ respectively). This is a common mistake when configuring the billing configuration.

Step 3: Configure Billing Plans

Update your billing configuration to include the details of your products:

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,
},
],
},
],
};

Allow switching between plans in the Customer Portal

In the Stripe Customer Portal, you can allow users to switch between plans. To do this, you need to configure the Customer Portal in the Stripe Dashboard.

For more information, please see the Stripe Billing Portal documentation.

Per-Organization Customers (B2B Billing)

When using Stripe, Makerkit supports true per-organization billing:

  • Users have users.stripeCustomerId (personal billing)
  • Organizations have organizations.stripeCustomerId (org billing)

Stripe customers are created and persisted automatically by the Better Auth Stripe plugin:

  • The org Stripe customer is created on the first organization checkout/upgrade
  • Organization name changes are synced to Stripe’s customer record
  • Organizations with active subscriptions are protected from deletion

If you want to customize customer creation metadata or logging, see packages/billing/stripe/src/stripe-plugin.ts.

Step 4: Configure Webhooks

Webhooks are essential for receiving real-time subscription events from Stripe.

Local Development

We provide a Docker command that will start the Stripe CLI and forward webhooks to your local server.

Please run it using the following command:

pnpm --filter web run stripe:listen

Note: if you don't want to use Docker, you can install the Stripe CLI globally on your machine and run the following command instead:

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

The first time you run this command, it will ask you to login to your Stripe account. Follow the instructions on the screen to do so.

Stripe CLI Login

Once you're logged in, please run the command again:

pnpm --filter web run stripe:listen

The command will output the webhook signing secret. Please copy the webhook signing secret (starts with whsec_) and add to .env.development or .env.local:

STRIPE_WEBHOOK_SECRET=whsec_...

Note: STRIPE_WEBHOOK_SECRET is effectively required for both development and production webhook processing. Without it, webhook signature verification cannot be performed and you will not be able to receive events from Stripe.

If you are testing against a remote server, then you need to set up a destination URL for the webhook from the Stripe Sandbox, effectively just like you would do for a production webhook. This is the URL that Stripe will send the webhook to. The guide below will walk you through how to do this.

Production Webhooks

When going to production, you will need to configure the webhook endpoint in the 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: Test the Integration

Test Checkout

Use Stripe test cards:

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

Test Trial Period

  1. Create a subscription with trial enabled
  2. Verify trial end date in Stripe Dashboard
  3. Check that status: 'trialing' appears in your app

Environment Variables Reference

Complete .env configuration for Stripe:

# 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 Best Practices

API Key Security

  • Never commit API keys to version control
  • Use different keys for test/production
  • Rotate keys periodically
  • Restrict API key permissions in Stripe Dashboard

Webhook Security

The plugin automatically verifies webhook signatures:

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

Production Checklist

Before going live:

  • [ ] Replace test API keys with live keys
  • [ ] Configure production webhook endpoint
  • [ ] Test all webhook events in production
  • [ ] Set up billing email notifications
  • [ ] Configure Customer Portal settings in Stripe Dashboard
  • [ ] Review Stripe Radar rules for fraud prevention

Common Pitfalls

  • Using Product ID instead of Price ID: The billing config requires Price IDs (price_...), not Product IDs (prod_...). This is the most common setup mistake.
  • Webhook secret mismatch: Local development and production use different webhook secrets. The CLI-generated secret won't work in production.
  • Forgetting to run stripe:listen during development: Without the webhook forwarder, subscription state won't sync after checkout completes.
  • Wrong webhook URL in production: The endpoint is /api/auth/stripe/webhook - not /api/stripe/webhook or /webhooks/stripe.
  • Missing required webhook events: At minimum, subscribe to checkout.session.completed, customer.subscription.created, customer.subscription.updated, and customer.subscription.deleted.
  • Test mode keys in production: Stripe test keys (sk_test_...) don't work with production. Switch to live keys (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 →