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
.envfile
Use Stripe test mode keys during development. Production verification can wait until launch.
Step 1: Obtain API Keys
Development Keys (Test Mode)
- Sign in to Stripe Dashboard
- Switch to Test mode or use the Sandbox
- Copy the Secret key (prefix:
sk_test_)

Add to your .env.local file:
STRIPE_SECRET_KEY=sk_test_51O...your_test_key_hereThis key is for testing only.
Step 2: Set Up Products and Prices
Via Stripe Dashboard

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:
- Navigate to Products → Add product
- Create a product for each tier (e.g., "Starter", "Pro", "Enterprise")
- For each product, add prices:
- Set billing period (monthly/yearly)
- Set amount
- Enable "Recurring" billing
- 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)

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:listenWithout Docker: Install Stripe CLI globally and run:
stripe listen --forward-to http://localhost:3000/api/auth/stripe/webhookFirst run prompts for Stripe login. Follow the on-screen instructions.

After logging in, run again:
pnpm --filter web run stripe:listenCopy 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:
- In Stripe Dashboard, navigate to Developers → Webhooks
- Click Add endpoint
- Set endpoint URL:
https://yourdomain.com/api/auth/stripe/webhook - Select events to listen for at least the following:
checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deleted
- More webhooks events may be required depending on your needs (eg. if you need to handle othr events)
- Copy the webhook signing secret
- Add to production environment variables (always prefer using the hosting provider's environment variables editor)
STRIPE_WEBHOOK_SECRET=whsec_...production_secretStep 5: Verify Integration
Test Checkout Flow
Stripe provides test cards:
| Card Number | Description |
|---|---|
4242 4242 4242 4242 | Successful payment |
4000 0025 0000 3155 | Requires 3D Secure authentication |
4000 0000 0000 9995 | Payment declined |
Verify Trial Functionality
- Start a subscription with trial enabled
- Confirm trial end date in Stripe Dashboard
- Verify
status: 'trialing'shows in your app
Environment Variables
Full Stripe configuration:
# Stripe API KeysSTRIPE_SECRET_KEY=sk_test_... # or sk_live_... for production# Webhook SecretSTRIPE_WEBHOOK_SECRET=whsec_...# Optional Stripe behavior toggles (defaults shown)CREATE_CUSTOMER_ON_SIGN_UP=trueSTRIPE_ENABLE_TRIAL_WITHOUT_CC=falseENABLE_AUTOMATIC_TAX_CALCULATION=falseENABLE_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 domainSecurity 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:listenlocally: Without the forwarder, subscription state doesn't sync after checkout. - Wrong webhook URL: The endpoint is
/api/auth/stripe/webhook, not/api/stripe/webhookor/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 tosk_live_...before launch.
Frequently Asked Questions
Where do I find my webhook signing secret?
Why aren't subscriptions syncing after checkout?
Can I use the same products for test and production?
How do I enable plan switching in the customer portal?
What's the difference between test mode and sandbox?
Next: Polar Setup →