> ## Documentation Index
> Fetch the complete documentation index at: https://supastack.dev/llms.txt
> Use this file to discover all available pages before exploring further.
> For the full documentation corpus in one file, fetch: https://supastack.dev/llms-full.txt

# Add Stripe payments

Create a free Stripe account, a product with a recurring price, a secret key, and the signed webhook that grants entitlements after checkout.

## Beginner walkthrough

Prerequisites: the app running locally (Ship in 5 minutes) and a migrated database for real accounts (Set up database). You need an email address for the Stripe account — no card and no charges. Everything in this guide happens in test mode: fake money, test card numbers, unlimited retries before going live.

Quick terms: test mode is Stripe's sandbox — the dashboard's Test mode toggle keeps every key, product, and payment fake until you switch it off. A secret key (`sk_test_…`) is your server's password to Stripe. A product with a price is what customers buy; a price id (`price_…`) names it in code. A webhook is Stripe calling your server when a payment succeeds or a subscription changes, and the signing secret (`whsec_…`) proves the call really comes from Stripe.

What you create: a Stripe account, one product with a recurring price, a secret API key, and a webhook endpoint — about twenty minutes. Stripe hosts the checkout page itself; the kit creates the session server-side and redirects, so no publishable browser key is needed.

Keep scope in mind: this guide sells web subscriptions only. Purchases inside the iOS/Android app must go through Apple and Google billing — never Stripe Checkout — which is why mobile uses RevenueCat instead.

[Set up the database](https://supastack.dev/docs/tanstack/setup-database.md)

[Set up RevenueCat for mobile purchases](https://supastack.dev/docs/mobile/revenuecat.md)


## Create the Stripe account

1. Open [stripe.com](https://stripe.com) → Start now and sign up with your email.

2. The onboarding asks about your business — for test mode you can skip every question and still use the full sandbox; complete activation only before taking real payments.

Expected: you land on the dashboard with the Test mode toggle visible in the header. Keep it on for everything below — live keys belong in your production secret store later, never in a laptop env file.


## Copy the API keys

1. In the dashboard open Developers → API keys.

2. You see two test keys: a Publishable key `pk_test_…` and a masked Secret key. Click Reveal test key next to the secret and copy the `sk_test_…` value.

The kit only needs the secret key: checkout sessions are created on the server and the user is redirected to Stripe's hosted page, so no publishable key lands in env vars. If a product you build later adds Stripe.js you would use the publishable key client-side; the kit's billing flow does not.

The `sk_test_…` key is a full credential for the sandbox account: it lives in `apps/web-tanstack/.env.local` or a secret store, never in `NEXT_PUBLIC_`/`EXPO_PUBLIC_` variables and never in Git.


## Create the product and price

1. Open Product catalog → Add product.

2. Name it after your plan — `Pro` matches the kit's shipped plan — give it a price, and choose Recurring (monthly, for example): the kit's checkout creates subscription-mode sessions, which require a recurring price, not a one-time one.

3. Save, open the product, and copy the price id — it starts `price_…` (not `prod_…`, which is the product id).

The shipped `pro` plan already shows a Monthly/Yearly toggle: monthly checks out with `STRIPE_PRICE_ID`, and the yearly option needs a second recurring price in `STRIPE_PRICE_ID_PRO_YEARLY` — without it, a yearly checkout fails `price-unconfigured`. Change plan names, amounts, and what a purchase grants in `packages/web-core/src/product-plans.ts` — Stripe stores the money side, the plan file stores the entitlement side.


## Add the webhook endpoint

The webhook is what actually grants access: the kit flips the entitlement only after a signed Stripe event arrives, so a checkout redirect alone proves nothing.

1. Open Developers → Webhooks → Add endpoint.

2. Endpoint URL: your public URL plus `/api/billing/webhook` — for example `https://yourdomain.com/api/billing/webhook`. The other billing providers get their own path under that directory; Stripe's receiver lives at the root.

3. Under Select events choose the events the kit handles: `checkout.session.completed`, `checkout.session.async_payment_succeeded`, `checkout.session.async_payment_failed`, `invoice.paid`, `invoice.payment_failed`, `customer.subscription.created`, `customer.subscription.updated`, and `customer.subscription.deleted`.

4. Create the endpoint, open its page, click Reveal under Signing secret, and copy the `whsec_…` value — each endpoint has its own secret.

Local development cannot receive Stripe's servers, so use the Stripe CLI instead: `stripe login` once, then `stripe listen --forward-to localhost:3000/api/billing/webhook` in a second terminal. It prints a temporary `whsec_…` — that value, not the dashboard one, goes in `.env.local` while `stripe listen` runs.


## Add the values to the app

Set the lines below in `apps/web-tanstack/.env.local`. `BILLING_PROVIDER=stripe` selects the adapter; the other three are the key, the price, and the signing secret. Enabling the provider with any of them missing fails startup and names the variable.

All three `STRIPE_*` values are server-only — never `NEXT_PUBLIC_`/`EXPO_PUBLIC_`, never committed; production gets them through the host's secret manager (the deploy guide covers Secret Manager). Restart the dev server after editing.

```
# apps/web-tanstack/.env.local
BILLING_PROVIDER=stripe
STRIPE_SECRET_KEY=sk_test_…
STRIPE_PRICE_ID=price_…
STRIPE_WEBHOOK_SECRET=whsec_…   # `stripe listen` value locally; endpoint signing secret in production
# STRIPE_PRICE_ID_PRO_YEARLY=price_…   # required for the shipped Yearly toggle
```


## Verify and troubleshoot

Start `stripe listen --forward-to localhost:3000/api/billing/webhook` and leave it running, restart the dev server, then open `/billing`, choose the Pro plan, and finish Stripe's hosted checkout with the test card `4242 4242 4242 4242`, any future expiry, and any CVC.

Expected: the app returns to `/billing?checkout=success` and the workspace's entitlement flips active — the listen terminal logs the delivered events, and Developers → Webhooks → your endpoint on the dashboard shows green deliveries.

To exercise the receiver without clicking through checkout, `stripe trigger checkout.session.completed` sends a synthetic event through the listen forwarder.

Common failures: a 400 on every delivery means the signing secret does not match that endpoint — recopy `whsec_…` for the right endpoint and update env; silent no-ops mean `BILLING_PROVIDER` is still `local`; and `sk_live_` keys mixed into a test setup mean you copied the wrong-mode keys — keep test and live strictly separate.

The kit ships the same one-contract adapter for Lemon Squeezy, Creem, Polar, Whop, Paddle, Dodo, and PayPal — same flow, different console and env names — and mobile purchases stay on RevenueCat, not Stripe.

[Compare the billing providers](https://supastack.dev/docs/tanstack/setup-payments.md)

[Add Resend email next](https://supastack.dev/docs/tanstack/resend-email.md)

[Mobile purchases use RevenueCat](https://supastack.dev/docs/mobile/revenuecat.md)


If you want to continue setup, read [Add Resend email](https://supastack.dev/docs/tanstack/resend-email.md).


## Next

[Add Resend email](https://supastack.dev/docs/tanstack/resend-email.md)
