Create new

Webhooks

Handling Stripe webhook events server-side.

All subscription state changes in Launch.now are driven by Stripe webhooks — not by the checkout redirect. This ensures your database stays consistent even when the user closes the browser mid-checkout.

Webhook endpoint

The webhook handler lives at app/api/webhooks/stripe/route.ts:

import { NextRequest, NextResponse } from "next/server";
import { stripe } from "@/lib/stripe";
import { handleStripeEvent } from "@/lib/stripe-events";

export async function POST(req: NextRequest) {
  const body = await req.text();
  const signature = req.headers.get("stripe-signature")!;

  let event: Stripe.Event;

  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!,
    );
  } catch {
    return NextResponse.json({ error: "Invalid signature" }, { status: 400 });
  }

  await handleStripeEvent(event);
  return NextResponse.json({ received: true });
}

Handled events

EventAction
checkout.session.completedActivate subscription, update status to active
customer.subscription.updatedSync plan changes, period end date
customer.subscription.deletedMark subscription as canceled
invoice.payment_succeededExtend currentPeriodEnd, send receipt via Inngest
invoice.payment_failedMark subscription as past_due, trigger retry email

Local webhook testing

Use the Stripe CLI to forward events to your local server:

# Install Stripe CLI, then:
stripe listen --forward-to localhost:3000/api/webhooks/stripe

The CLI outputs a webhook signing secret (whsec_...) — use this as STRIPE_WEBHOOK_SECRET in your .env.local during development.

Never use your production webhook secret in local development. The Stripe CLI generates a separate secret for local forwarding.