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:
App Router (Recommended for new projects)
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:
- → Stripe Webhook Integration - Signatures, retries, idempotency
- → Clerk Webhook Verification - Svix signatures, user events
- → GitHub Webhook Integration - HMAC signatures, event types
- → Shopify Webhook Handler - HMAC-SHA256, mandatory webhooks
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:
- Route setup: App Router route handlers or Pages Router API routes
- Signature verification: Validate requests come from the real provider
- Idempotency: Handle retried events safely
- Timeouts: Respond quickly, process asynchronously if needed
- 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.