Skip to content
Guide Integrations

Handling Webhooks in Next.js App Router: Raw Body, Signatures & Async Processing

How to build reliable webhook handlers in Next.js (App Router Route Handlers): reading req.text() for HMAC signature verification, edge vs node runtime, and avoiding timeouts.

Published 2 min read
On this page

Next.js App Router provides modern Route Handlers (app/api/webhooks/[provider]/route.ts) based on the Web Standard Request and Response APIs.

The most common mistake developers make when building webhook receivers in Next.js is calling await req.json() before signature verification. Because cryptographic HMAC algorithms require the exact, unmutated bytes of the original request, parsing JSON first alters whitespace and character encoding, resulting in signature verification failure.

The Standard Pattern: req.text()

In Next.js Route Handlers, use await req.text() to get the raw body string:

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

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

export async function POST(req: Request) {
  const body = await req.text(); // Exact raw string for signature
  const signature = req.headers.get('stripe-signature');

  if (!signature) {
    return NextResponse.json({ error: 'Missing signature' }, { status: 400 });
  }

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err: any) {
    return NextResponse.json({ error: `Webhook Error: ${err.message}` }, { status: 400 });
  }

  // Handle the event
  switch (event.type) {
    case 'checkout.session.completed':
      // Trigger async processing
      break;
  }

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

Runtime Considerations: Node.js vs Edge

By default, Route Handlers run on the Node.js runtime (export const runtime = 'nodejs'), which supports full Node cryptographic APIs (node:crypto) and standard SDKs.

If running on Edge Runtime (export const runtime = 'edge'), ensure your verification library uses the Web Crypto API (crypto.subtle) rather than native Node modules.

Avoiding Serverless Function Timeouts

Vercel and serverless platforms terminate serverless executions after 10-15 seconds (on Hobby/Pro plans). If your webhook handler runs database migrations or external API calls, the execution may time out, prompting the webhook provider to retry.

  • Return 200 OK as soon as the signature is verified and the event ID is recorded.
  • Offload long-running tasks to background queues (Inngest, QStash, BullMQ) or Vercel waitUntil().
Get started

See what happened to every webhook.

HookWatch keeps the request, the response, and every attempt for each delivery — so the debugging, retry, and replay steps in this article are a matter of reading, not reconstructing.