Stripe integration: setup to first payment
Getting paid is the hard-easy part.
Stripe is the standard for SaaS subscriptions. It's powerful and secure, but the setup has fine details. This guide walks through the essentials and the pitfalls.
- 6
- Parts, end to end
- 2
- Keys, one of them secret
- 1
- Webhook you cannot skip
- $0
- Until you charge someone
Part 1: create a Stripe account and get your keys
Go to stripe.com and sign up. You'll get two API keys: the publishable key (safe to expose in frontend code) and the secret key (never expose it; keep it in .env only).
Stripe also gives you a webhook signing secret (used to verify that webhook events are genuinely from Stripe).
Keep these in a .env.local file:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...Part 2: create your products and prices in Stripe
In the Stripe dashboard, create a product ("Pro Plan") and add a price ($99/month or $990/year).
You'll get a price ID (price_1A2b3c4d...). This ID goes into your database and your code.
Best practice: keep the price ID in your database alongside your plan definition. Don't hardcode Stripe IDs in your code. Always look them up from a safe source (the database or config).
Part 3: create a subscription session
When the user clicks "Subscribe," you create a Stripe Checkout session. This session is a URL you redirect the user to. They enter their payment details on Stripe (secure) and return to your site.
Server-side code (a Next.js API route) creates a session with the customer's email, the requested items (price ID), the mode (subscription), and success and cancel URLs.
Part 4: handle webhook events
When a user completes payment, Stripe sends a webhook event (checkout.session.completed). This event triggers your server to: (1) verify the subscription succeeded. (2) create a subscription record in your database. (3) activate the user's account.
Without webhooks, you might miss subscription starts (the user completes payment but your server crashes before saving). Webhooks are the reliable way.
Part 5: handle failure and retries
Users will fail to pay for reasons: an invalid card, an expired card, insufficient funds. Stripe retries automatically (3 times over 5 days). But you need to handle the payment-failed event.
Listen for invoice.payment_failed and email the user: "Your payment failed. Update your payment method here: [link]."
Always give users a way to update their payment method (the Stripe customer portal).
Part 6: test before launch
Use Stripe test mode (keys start with pk_test_ and sk_test_). In test mode, you can subscribe without a real credit card.
Test card numbers (from the Stripe docs):
- Success: 4242 4242 4242 4242 (any future expiry, any CVC)
- Declined: 4000 0000 0000 0002 (declined for insufficient funds)
- Requires authentication: 4000 0000 0000 3220 (requires 3D Secure)
Test the full flow: subscribe → payment success → database update → verify the subscription was created.
Common mistakes
Mistake one: exposing the secret key in frontend code. Never. Keep it in .env.local (server only).
Mistake two: not verifying webhook signatures. Always call stripe.webhooks.constructEvent() to verify.
Mistake three: relying only on client-side success confirmation. Always confirm via webhook.
Mistake four: not handling payment failure. The user's card is declined, no reminder is sent, the subscription goes dark.
Mistake five: hardcoding price IDs. Keep them in the database so you can change pricing without deploying.
Tools for this
Each one is a single file you save and open by double-click. It works with the machine offline, and what you paste into it never leaves your computer.
The integration is not the hard part. The failure paths after the first charge are.
Start building your productMore guides
- Hire an engineer or work with a studio?The right choice depends on your stage, budget, and needs.7 min read
- What AI costs to run, and how to price it inAI is priced per word, and almost nobody costs it that way.8 min read
- AI and your company data: what to never pasteThe risk is not the model. It is the tier you are on and the file you paste.8 min read
- Turn support tickets and reviews into decisionsYour customers already told you what to fix. It is sitting in the inbox, unread as a set.8 min read
- Managing AI in a team that already worksThe tools are not the hard part. Deciding who owns the output is.8 min read
- SEO for a brand-new site: the first 90 daysA new domain has no authority. That changes the order of everything.10 min read