Connect
Stripe
Add a price, point the webhook at Launch Kit, and keep plan copy in config.ts. Checkout and access are already wired.
The pricing card calls ButtonCheckout. That hits /api/stripe/create-checkout, opens Stripe Checkout, and comes back to the page the customer was on. The webhook then writes customer_id, price_id, and has_access on profiles.
Do not rebuild Checkout. Set the price ID and the webhook secret.
Keys
In the Stripe Dashboard:
| Credential | Env var | Where to get it |
|---|---|---|
| Secret key | STRIPE_SECRET_KEY | Developers → API keys |
| Webhook signing secret | STRIPE_WEBHOOK_SECRET | Developers → Webhooks → your endpoint |
| Price ID | NEXT_PUBLIC_STRIPE_PRICE_ID | Product catalog → the price (price_...) |
STRIPE_PUBLIC_KEY is in .env.example and unused. Skip it.
Create the product
- Product catalog → Add product
- One-time price for a lifetime licence, or recurring for a subscription
- Copy the Price ID into
NEXT_PUBLIC_STRIPE_PRICE_ID - Match
stripe.plans[0].priceinconfig.tsto what Stripe charges so the card is honest
ButtonCheckout defaults to mode="payment". For a subscription price, pass mode="subscription" on the button. The webhook already handles both.
Webhook
Point Stripe at:
https://your-domain.com/api/webhook/stripeUse the Stripe CLI:
stripe listen --forward-to localhost:3000/api/webhook/stripePaste the whsec_... value it prints into STRIPE_WEBHOOK_SECRET.
Developers → Webhooks → Add endpoint. URL is /api/webhook/stripe. Paste the signing secret into your host’s env vars.
Select these events. The route already handles them:
| Event | What happens |
|---|---|
checkout.session.completed | Match the plan, find or create the user, set access, send the welcome email |
customer.subscription.deleted | Set has_access to false |
invoice.paid | Re-grant access when the invoice price matches price_id |
checkout.session.expired | No-op |
customer.subscription.updated | No-op |
invoice.payment_failed | No-op. Stripe retries, then sends customer.subscription.deleted |
Signature verification is required. Do not skip it.
Customer portal
Turn on Settings → Billing → Customer portal. ButtonAccount on the dashboard opens it after the customer has a customer_id. Receipts and cancellations stay in Stripe.
Test it
profilestable exists (Supabase)- Webhook is forwarding
- Use
4242 4242 4242 4242in test mode - Confirm
has_accessistrueon the profile
Then gate the dashboard. The flag is written. Your product still has to read it.