Skip to main content
Back to Blog
How Tos & TutorialsJanuary 16, 2025

Stripe Webhooks in LaunchKit: Complete Setup Guide

Set up Stripe webhooks for local development and production. Handle checkout.session.completed, verify signatures, and debug common issues.

LaunchKit Team

Building tools for makers

Stripe webhooks setup guide for Next.js

What Are Webhooks?

Webhooks are Stripe's way of telling your app when something happens. Customer completed checkout? Webhook. Subscription renewed? Webhook. Payment failed? Webhook.

Without webhooks, your app would need to constantly poll Stripe asking "did anything happen yet?" Instead, Stripe pushes events to an endpoint you control, and your code reacts accordingly.

How LaunchKit Handles Webhooks

LaunchKit's webhook handler lives at /app/api/webhook/stripe/route.ts. When Stripe sends an event, this route:

  • Verifies the signature (proves the request came from Stripe)
  • Parses the event type
  • Updates your Supabase database accordingly
  • Returns a 200 response so Stripe knows it succeeded

The key event is checkout.session.completed—this fires when a customer successfully pays, and LaunchKit uses it to grant access by updating the user's profile.

Local Development Setup

Step 1: Install the Stripe CLI

The Stripe CLI forwards webhook events from Stripe to your local machine.

# macOS
brew install stripe/stripe-cli/stripe

# Windows (with scoop)
scoop install stripe

# Or download from stripe.com/docs/stripe-cli

Step 2: Authenticate

Link the CLI to your Stripe account:

stripe login

This opens a browser window. Authorize the CLI and you're connected.

Step 3: Forward events locally

Start the forwarding with:

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

The CLI outputs a webhook signing secret starting with whsec_. Copy this and add it to your .env.local:

STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxx

Step 4: Test with a real payment

Make sure your app is running (npm run dev), then complete a test checkout. Use Stripe's test card:

Card: 4242 4242 4242 4242
Expiry: Any future date
CVC: Any 3 digits

Watch the CLI output—you should see the checkout.session.completed event arrive, and your terminal will show whether the webhook handler returned 200 or an error.

Production Setup

Step 1: Create the webhook endpoint

In your Stripe Dashboard → Developers → Webhooks, click "Add endpoint" and enter:

Endpoint URL: https://yourdomain.com/api/webhook/stripe

Step 2: Select events

Click "Select events" and choose the events LaunchKit handles:

  • checkout.session.completed — Customer paid successfully
  • customer.subscription.updated — Subscription changed
  • customer.subscription.deleted — Subscription cancelled

For one-time purchases, only checkout.session.completed is essential.

Step 3: Copy the signing secret

After creating the endpoint, click on it and reveal the "Signing secret". Copy this whsec_... value and add it to your production environment variables in Vercel.

STRIPE_WEBHOOK_SECRET=whsec_live_xxxxxxx

Understanding the Webhook Handler

Here's what happens inside LaunchKit's webhook route:

// 1. Get the raw body (required for signature verification)
const body = await request.text();
const signature = request.headers.get("stripe-signature");

// 2. Verify the signature
const event = stripe.webhooks.constructEvent(
  body,
  signature,
  process.env.STRIPE_WEBHOOK_SECRET
);

// 3. Handle the event
if (event.type === "checkout.session.completed") {
  const session = event.data.object;
  const customerId = session.customer;
  const customerEmail = session.customer_email;

  // Update user's access in Supabase
  await supabase
    .from("profiles")
    .update({ has_access: true, customer_id: customerId })
    .eq("email", customerEmail);
}

// 4. Return 200 to acknowledge receipt
return NextResponse.json({ received: true });

Important: Use request.text()

Next.js 15 automatically parses JSON bodies, but Stripe signature verification needs the raw string. Always use request.text() instead of request.json() for webhook routes.

Debugging Webhook Issues

Check the Stripe Dashboard

Go to Developers → Webhooks and click on your endpoint. You'll see a list of recent webhook attempts with their HTTP status codes:

  • 200: Success
  • 400: Usually a signature verification failure
  • 500: Your code threw an error

Click any failed attempt to see the request payload and your server's response.

Common issues

  • "Webhook signature verification failed": Wrong STRIPE_WEBHOOK_SECRET. Make sure you're using the secret from the correct endpoint (CLI for local, dashboard for production).
  • Webhook never arrives: For local dev, make sure the Stripe CLI is running. For production, check that your endpoint URL is correct and your server is accessible.
  • "No such customer": You might be using test mode events with live mode keys, or vice versa.
  • User not found in database: The webhook fires before the user exists in your database. LaunchKit handles this by matching on email, which Stripe includes in the checkout session.

Testing Without Real Payments

You can trigger test events directly from the Stripe CLI:

# Trigger a checkout.session.completed event
stripe trigger checkout.session.completed

# See all available event types
stripe trigger --help

This sends a synthetic event to your local webhook endpoint—useful for testing your handler without completing actual checkouts.

Security Best Practices

  • Always verify signatures: Never trust incoming webhooks without verification. Anyone could send a POST to your endpoint.
  • Use HTTPS in production: Stripe won't send webhooks to HTTP endpoints (except localhost).
  • Respond quickly: Stripe expects a response within 20 seconds. For long-running tasks, acknowledge the webhook first, then process in the background.
  • Handle retries: Stripe retries failed webhooks for up to 3 days. Make your handlers idempotent—running them twice shouldn't cause problems.

Quick Reference

Keep this handy during setup:

# Local development
stripe listen --forward-to localhost:3000/api/webhook/stripe

# Test card for checkout
4242 4242 4242 4242 | Any future date | Any CVC

# Trigger test event
stripe trigger checkout.session.completed

# Check webhook logs
https://dashboard.stripe.com/webhooks

Ready to ship faster?

LaunchKit gives you auth, payments, CRM, and everything you need to launch your SaaS in days, not months.

Get LaunchKit

Written by

LaunchKit Team

We're a small team passionate about helping developers and entrepreneurs ship products faster. LaunchKit is our contribution to the maker community.

Related Articles