STRIPE STARTER · GETTING STARTED
Getting started
Clone, paste two Stripe keys, run `stripe listen`, run `bun dev`. The whole setup is under ten minutes if you've got a Stripe account.
Prerequisites
- Node 20 or newer.
- Bun (npm install was slowing us down — we switched and never looked back).
- A Stripe account. Free, no card to sign up.
- The Stripe CLI — for local webhook forwarding. macOS: brew install stripe/stripe-cli/stripe. Elsewhere: stripe.com/docs/stripe-cli.
1. Clone & install
git clone git@github.com:peal-dev/your-stripe-starter.git
cd your-stripe-starter
bun installYes, it has to be bun
package.json scripts assume bun and the lockfile is bun.lock. npm install will technically work but you'll fight subtle dependency resolution differences. Use bun.
2. Grab your Stripe keys
Sign in at dashboard.stripe.com → make sure the test toggle (top right) is ON → Developers → API keys. Copy the publishable key (pk_test_…) and the secret key (sk_test_…).
3. Copy env, paste keys
cp .env.example .env.local# Required to boot:
STRIPE_SECRET_KEY="sk_test_..."
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."
STRIPE_WEBHOOK_SECRET="whsec_..." # we fill this in step 4
# Optional (rate limiting):
# UPSTASH_REDIS_REST_URL=...
# UPSTASH_REDIS_REST_TOKEN=...sk_ is secret, pk_ is publishable. Mixing them up means boot fails.
Our Zod schema in src/lib/env.ts validates the prefixes. If you paste the publishable key into STRIPE_SECRET_KEY, the app refuses to start with a useful error. Believe the error — don't think it's wrong.
4. Start the Stripe CLI listener
In a separate terminal, forward Stripe webhooks to your local dev server. The CLI prints a webhook signing secret — paste it into .env.local as STRIPE_WEBHOOK_SECRET. This secret is DIFFERENT from your production secret. It rotates each time you re-run `stripe listen`.
stripe listen --forward-to localhost:3000/api/webhooks/stripe
# > Ready! Your webhook signing secret is whsec_xxx (^C to quit)Keep `stripe listen` running
Without it, webhooks from your test payments will never reach your dev server. The local webhook secret only works while the CLI session is active. Close it and the secret stops being valid.
5. Pick a payment mode
Open payments.config.ts at the project root. Set mode to one of "one-time", "subscription", or "hybrid". The whole app — pricing UI, webhook filter, checkout session params — adapts to whatever you pick.
// payments.config.ts
const config: PaymentsConfig = {
mode: "subscription", // "one-time" | "subscription" | "hybrid"
customerPortal: { enabled: true },
redirects: {
success: "/checkout/success",
cancel: "/checkout/cancel",
},
};6. Replace the placeholder price IDs
Open src/config/pricing.ts. Every plan ships with a price_REPLACE_* placeholder. Create real Stripe Prices (dashboard.stripe.com → Products → Add product → set up a price), copy the IDs, paste them in. The Server Action allowlists against this file — a placeholder will fail a real checkout.
7. Run it
bun devOpen http://localhost:3000/pricing. Click a plan → you should be redirected to Stripe Checkout. Pay with the test card 4242 4242 4242 4242, any future expiry, any CVC, any zip. Watch the Stripe CLI terminal — you should see the webhook events stream through.
If you got this far, you're done
You've got a working checkout, a verified webhook, and an idea of which mode you're in. Everything else in these docs is additive — wire your DB, send your emails, restyle the UI.