Prerequisites
- A Stripe account (stripe.com) — free, no business entity required for test mode.
- The Stripe CLI for local webhook forwarding — docs.stripe.com/stripe-cli.
- A live Postgres database (
pnpm db:pushalready run).
1. Set the active provider
Opensrc/config/site.ts:
'stripe'.
2. Create products + prices in Stripe Dashboard
In the Stripe Dashboard go to Products → Add product. Create one product per scene you want to sell. For the vibestrap scaffold itself you need two prices on the same product (or two separate products):| Scene | Description |
|---|---|
| Vibestrap promo | Limited-time price (e.g. $49 one-time) |
| Vibestrap standard | Regular price (e.g. $99 one-time) |
pro_monthly recurring price,
a pro_yearly recurring price, a lifetime one-time price, and 1–4
credits_* one-time prices.
Copy each price_… ID — you’ll paste them into env vars next.
3. Set env vars
In.env.local, fill in (names match src/env.ts exactly):
4. Local webhook testing with Stripe CLI
whsec_…. Paste it into
STRIPE_WEBHOOK_SECRET in .env.local and restart pnpm dev. Now any test
payment you make will deliver an event to your local route, signed with the
correct secret.
To trigger an event without paying through the UI:
5. Production webhook endpoint
In the Stripe Dashboard: Developers → Webhooks → Add endpoint.- URL:
https://your-domain.com/api/webhooks/stripe - Events (minimum):
checkout.session.completed,invoice.paid,customer.subscription.updated,customer.subscription.deleted. - After creating, click into the endpoint and copy its Signing secret
(
whsec_…). Set it asSTRIPE_WEBHOOK_SECRETin your production env.
Verify it works
- Start dev with
stripe listenrunning. - Open
/pricing, click “Get vibestrap” — you should land on Stripe Checkout. - Pay with
4242 4242 4242 4242, any future expiry, any CVC, any ZIP. - You should be redirected back to your
successUrl. - Check the database:
You should see
provider='stripe',status='paid', your test amount. - Re-run
stripe trigger checkout.session.completed— no duplicate row, thanks to thepayment.invoiceId/sessionIdidempotency check.
Common pitfalls
- Webhook secret mismatch — the secret printed by
stripe listenis ephemeral (rotates each session). Don’t paste it into production. Use the one from the Dashboard endpoint. - Test vs live confusion — test-mode
sk_test_*and live-modesk_live_*prices are not interchangeable. Each price ID exists in only one mode. - Tax not configured — if you sell to the EU and don’t enable Stripe Tax, you’ll owe VAT out of pocket. Either flip Stripe Tax on or use a Merchant of Record (Paddle / Lemon Squeezy).
- Missing
successUrl/cancelUrl—createCheckoutthrows if either is empty. They must be absolute URLs (https://…). - Subscriptions without
subscription_data.metadata— recurring renewals emitinvoice.paidevents whosemetadatais empty. The Stripe provider copies checkoutmetadataintosubscription_data.metadataso renewals carry youruserId/sceneforward — don’t strip that path.
Official docs
- Stripe Docs: stripe.com/docs
- Stripe CLI: docs.stripe.com/cli
- Webhooks reference: docs.stripe.com/webhooks
- Test cards: docs.stripe.com/testing
- Stripe Tax: stripe.com/tax