Limited offer: $250 off for the first 200 customers!Claim discount

Set up Stripe

Wire up Stripe end-to-end — products, checkout, webhook, and access control.

NoteThis tutorial uses Stripe. The flow for Lemon Squeezy is nearly identical — just swap the provider in config and follow the Payments feature guide.

1. Set the provider

// src/config.ts
paymentProvider: "stripe",

2. Add your Stripe keys

# .env.local
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...   # fill in after step 5

Find your secret key in Stripe Dashboard → Developers → API Keys.

3. Create products in Stripe

  • Stripe Dashboard → Product catalog → Add product.
  • For each plan, create a price — choose One time or Recurring.
  • Copy each price ID — it starts with price_.

4. Paste the price IDs into config

// src/config.ts
stripe: {
  plans: [
    { name: "Starter",  priceId: "price_xxx", mode: "payment" },
    { name: "Advanced", priceId: "price_yyy", mode: "subscription" },
    { name: "Pro",      priceId: "price_zzz", mode: "subscription" },
  ],
},

mode: "payment" is a one-time charge. mode: "subscription" is recurring billing.

WarningEach name must match a planName in config.pricing.cards exactly — this is how checkout buttons know which product to charge for.

5. Register the webhook

  • Stripe Dashboard → Developers → Webhooks → Add endpoint.
  • URL: https://yourdomain.com/api/webhook/stripe
  • Events to listen for: checkout.session.completed, customer.subscription.deleted, customer.subscription.updated, invoice.payment_failed
  • Copy the signing secret and add it as STRIPE_WEBHOOK_SECRET.

6. Test locally with Stripe CLI

stripe login
stripe listen --forward-to localhost:3000/api/webhook/stripe

The CLI outputs a temporary webhook secret for local dev. In another terminal:

stripe trigger checkout.session.completed

After the event fires, check the profilestable in Supabase — the test user's row should show has_access = true.

7. Gate your product behind access

Inside any server component or API route, check the has_access flag:

import { createClient } from "@/libs/supabase/server";

const supabase = await createClient();
const { data: profile } = await supabase
  .from("profiles")
  .select("has_access")
  .single();

if (!profile?.has_access) {
  redirect("/");
}