What you’re setting up
Four things, that’s it:- A product or payment link in Stripe
- A checkout URL saved in your env vars
- A webhook endpoint in Stripe
- A way for the repo to understand what was purchased
Env vars
Setup
Create your Stripe products
Head to your Stripe Dashboard and open Product catalog.
- Create a product for each thing you want to sell
- Add a price for each product
- Create a payment link or checkout flow
- One Medium plan product
- One Small credits product
Decide how Stripe describes the purchase
You have two options here. Pick whichever feels more natural to you.
- Option A: Metadata (recommended)
- Option B: Product map in env
Add a
type field to your product or checkout session metadata. This is the easiest to reason about because the purchase explains itself.Examples:type=plan-mediumtype=credits-small
session.metadata.typefirstproduct.metadata.typeas fallback
Add your checkout URLs
Paste the Stripe payment link into the matching env var in These are the URLs your app buttons will send users to.
.env.local:Add the webhook in Stripe
Back in Stripe:
- Go to Developers -> Webhooks
- Click Add endpoint
- Set the URL to:
- Enable at least these events:
checkout.session.completedcheckout.session.async_payment_succeeded
- Copy the signing secret into your env:
How it works under the hood
Once Stripe sends a successful webhook, here’s what happens:- The repo verifies the signature
- It loads the checkout session
- It resolves the purchase type from metadata or
STRIPE_PRODUCT_MAP - It stores the purchase in the database
- It updates the user’s credits or plan state
Verify your setup
Your Stripe setup is working if all of these are true:
- The app opens the Stripe checkout page when you click a payment button
- Stripe shows a successful webhook delivery in the dashboard
- A row appears in your
purchasestable - A credits purchase increases the user’s credits
- A plan purchase updates the user’s purchase state
Using the publishable key instead of the secret key
Using the publishable key instead of the secret key
STRIPE_SECRET_KEY needs your secret key (starts with sk_), not the publishable one (starts with pk_). The publishable key is for client-side Stripe.js only.Checkout works but nothing happens after payment
Checkout works but nothing happens after payment
You probably added the checkout URL but forgot the webhook. Without a webhook, Stripe can’t tell your app the payment succeeded.
Webhook receives events but purchase type is unknown
Webhook receives events but purchase type is unknown
You haven’t set metadata on your product/session and you haven’t set
STRIPE_PRODUCT_MAP. The repo needs at least one of these to know what was purchased.Webhook URL returns errors in production
Webhook URL returns errors in production
Make sure you’re using the correct production domain in the webhook URL, not
localhost.Payments Overview
Go back to the shared payment architecture and basics.

