Guide

Stripe integration: from setup to first payment

Getting paid is the hard-easy part.

8 min read·Back to guides
A card payment being made

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.

The integration is not the hard part. The failure paths after the first charge are.

Start building your product

Have a question?

We're here to help. Get in touch and let's talk.