stripetutorialsetup

How to configure Stripe webhooks

A step-by-step guide to setting up Stripe webhooks for your FounderKit app — locally and in production.

Marcos Ramos·February 3, 2026· 6 min read

Stripe webhooks are how Stripe tells your app that something happened — a payment succeeded, a subscription was cancelled, a payment failed. If you don't set them up correctly, your app won't update user plans.

Here's how to do it right with FounderKit.

What events you need

FounderKit listens for four Stripe events:

| Event | What it does | |-------|-------------| | checkout.session.completed | User subscribed → update plan to PRO/ENTERPRISE | | customer.subscription.updated | Plan changed → update stripePriceId and period end | | customer.subscription.deleted | Subscription cancelled → reset to FREE | | invoice.payment_failed | Payment failed → send failure email |

Local development

Install the Stripe CLI:

brew install stripe/stripe-cli/stripe
stripe login

Forward events to your local server:

stripe listen --forward-to localhost:3000/api/webhooks/stripe

Copy the webhook signing secret it shows you and add it to .env.local:

STRIPE_WEBHOOK_SECRET=whsec_...

Production setup

  1. Go to dashboard.stripe.com/webhooks
  2. Click "Add endpoint"
  3. Set the URL to https://your-domain.com/api/webhooks/stripe
  4. Select these events:
    • checkout.session.completed
    • customer.subscription.updated
    • customer.subscription.deleted
    • invoice.payment_failed
  5. Copy the signing secret and add it to your Vercel env vars

Testing locally

Run your dev server and the Stripe CLI listener in separate terminals:

# Terminal 1
npm run dev

# Terminal 2
stripe listen --forward-to localhost:3000/api/webhooks/stripe

Then trigger a test event:

stripe trigger checkout.session.completed

You should see the webhook handled in your server logs.

Common mistakes

Wrong signing secret — the local whsec_ from stripe listen is different from the production one in the Stripe dashboard. Use the right one for each environment.

Missing export const dynamic = "force-dynamic" — without this, Next.js may cache the webhook route. It's already set in FounderKit's route.ts.

Not handling invoice.payment_failed — users whose cards decline don't always cancel immediately. Stripe retries the charge. Handle this event to send a "please update your payment method" email.


If you run into issues, check the Stripe CLI logs — they show the exact payload and response code for every event.