Get Started

The Complete Guide to Webhooks in Next.js

Everything you need to know about handling webhooks in Next.js: App Router, Pages Router, signature verification, retries, timeouts, and production debugging.

The Complete Guide to Webhooks in Next.js#

Webhooks are how external services notify your application about events—a Stripe payment succeeds, a GitHub push happens, a Clerk user signs up. In Next.js, you handle webhooks by creating API routes (Pages Router) or route handlers (App Router) that receive HTTP POST requests from these services.

This guide covers everything you need to know about webhook handling in Next.js, from basic setup to production reliability patterns.

Quick Start: Your First Webhook Handler#

Here’s a minimal webhook handler in Next.js App Router:

// app/api/webhooks/route.ts
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  const payload = await request.json();

  // Process the webhook event
  console.log('Received webhook:', payload.type);

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

This works for development, but production webhooks require signature verification, error handling, and often idempotency. The rest of this guide covers these patterns.

Choosing Your Router: App Router vs Pages Router#

Next.js offers two routing approaches, each with different webhook patterns:

Route handlers in the app directory use the Web Request/Response API:

// app/api/webhooks/stripe/route.ts
export async function POST(request: Request) {
  const body = await request.text(); // Raw body for signature verification
  const signature = request.headers.get('stripe-signature');
  // ...
}

Building Webhook Route Handlers in Next.js App Router

Pages Router (Legacy projects)#

API routes in the pages directory use Node.js-style request/response:

// pages/api/webhooks/stripe.ts
export default function handler(req, res) {
  const body = req.body;
  const signature = req.headers['stripe-signature'];
  // ...
}

Pages Router vs App Router: Webhook Migration Guide

Signature Verification#

Webhook providers sign their payloads to prove authenticity. You must verify signatures before processing events—otherwise attackers could forge webhook requests.

Why Raw Body Access Matters#

Signature verification requires the exact bytes the provider sent. If your framework parses the JSON before verification, the signature check fails because the reconstructed JSON may differ from the original.

Accessing Raw Request Body for Webhook Signature Verification

Stripe Example#

import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

export async function POST(request: Request) {
  const body = await request.text();
  const signature = request.headers.get('stripe-signature')!;

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    return new Response('Invalid signature', { status: 400 });
  }

  // Process verified event
  switch (event.type) {
    case 'checkout.session.completed':
      // Handle successful checkout
      break;
  }

  return new Response('OK', { status: 200 });
}

Stripe Webhook Signature Verification in Next.js

Handling Retries and Duplicates#

Webhook providers retry failed deliveries. Your handler may receive the same event multiple times—you need idempotency to handle this safely.

The Idempotency Pattern#

export async function POST(request: Request) {
  const event = await verifyAndParse(request);

  // Check if we've already processed this event
  const existing = await db.query.processedEvents.findFirst({
    where: eq(processedEvents.eventId, event.id)
  });

  if (existing) {
    // Already processed - return success without reprocessing
    return new Response('OK', { status: 200 });
  }

  // Process the event
  await processEvent(event);

  // Record that we processed it
  await db.insert(processedEvents).values({
    eventId: event.id,
    processedAt: new Date()
  });

  return new Response('OK', { status: 200 });
}

How to Handle Webhook Retries and Duplicates in Next.js Stripe Webhook Idempotency in Next.js

Timeouts and Long-Running Processing#

Serverless platforms have execution time limits. Vercel’s default is 10 seconds; Stripe expects responses within 20 seconds. If your webhook processing takes longer, you’ll hit timeouts.

The Queue Pattern#

For complex processing, acknowledge the webhook immediately and process asynchronously:

export async function POST(request: Request) {
  const event = await verifyAndParse(request);

  // Queue for async processing - respond immediately
  await queue.add('process-webhook', { eventId: event.id, payload: event });

  // Respond within timeout
  return new Response('OK', { status: 200 });
}

Fix Stripe Webhook Timeouts on Vercel and Edge Functions Vercel Webhook Timeout Solutions

Next.js Version-Specific Considerations#

Next.js 15#

Next.js 15 changed how request APIs work—headers and cookies are now async. Webhook handlers need updates:

// Next.js 15
const headersList = await headers();
const signature = headersList.get('stripe-signature');

What’s New for Webhooks in Next.js 15

Next.js 14#

Next.js 14 made App Router the default. If you’re migrating from Pages Router, webhook handlers need restructuring.

What Changed for Webhooks in Next.js 14

Provider-Specific Guides#

Different webhook providers have different signing methods, retry policies, and quirks:

Debugging Webhook Failures#

When webhooks fail in production, debugging is difficult:

  • Provider logs show “delivered” but your system shows no record
  • Errors happen after you’ve returned a response
  • Serverless cold starts cause intermittent timeouts
  • No way to replay failed events without asking the provider

Next.js Webhook Not Working? Common Causes and Fixes How to Debug Webhook Failures in Next.js

Production Reliability with HookRelay#

For applications where webhook reliability matters operationally, HookRelay provides infrastructure that handles the hard parts:

  • Delivery tracking: Persistent record of what your handler received and responded
  • Failure visibility: See exactly why processing failed, not just that it failed
  • Replay capability: Re-send failed webhooks after fixing issues
  • Retry management: Separate from provider retry logic

Instead of providers sending webhooks directly to your Next.js app, they send to HookRelay, which forwards to your handler while maintaining complete delivery history.

Provider → HookRelay → Your Next.js App

        Delivery records
        Failure tracking
        Replay capability

See how delivery attempts are tracked and how failures become visible.

Summary#

Webhook handling in Next.js involves:

  1. Route setup: App Router route handlers or Pages Router API routes
  2. Signature verification: Validate requests come from the real provider
  3. Idempotency: Handle retried events safely
  4. Timeouts: Respond quickly, process asynchronously if needed
  5. Debugging: Have visibility into what’s actually happening

For production applications, consider whether you need dedicated webhook infrastructure (like HookRelay) or if the built-in patterns are sufficient for your reliability requirements.

Stop debugging webhook failures in production

Track delivery attempts, inspect failures, and replay events without rebuilding infrastructure

Prefer to click around first? Open the dashboard.

Learn more