Skip to content
Receiver guide

Build a webhook handler in Next.js

A Next.js App Router route handler that reads the raw request body (not the parsed JSON) so signature verification works, then acknowledges fast.

In the Next.js App Router, a webhook is just a POST route handler. The catch is reading the body correctly: use await req.text() to get the raw string, not req.json(), or signature verification will fail.

A minimal route handler

Put the handler at app/api/webhooks/route.ts and export an async POST. Read the raw body once, verify it, acknowledge with 200, then process asynchronously. Reject bad signatures with 401.

// app/api/webhooks/route.ts  (App Router route handler)
import crypto from 'node:crypto';

const SECRET = process.env.WEBHOOK_SECRET!;

export async function POST(req: Request) {
  const raw = await req.text();     // RAW body — not req.json()
  const sig = req.headers.get('x-webhook-signature');

  if (!verify(raw, sig)) {
    return new Response('bad signature', { status: 401 });
  }

  // Kick off work without blocking the response.
  queueMicrotask(() => process(JSON.parse(raw)));
  return new Response('ok');        // fast 2xx
}

function verify(raw: string, sig: string | null) {
  if (!sig) return false;
  const expected = crypto.createHmac('sha256', SECRET).update(raw).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(sig);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Notes

  • The pattern mirrors the Node/Express receiver — same raw-body + constant-time-compare rules, different framework.
  • For heavier work, push the job to a real queue instead of queueMicrotask so a crash doesn’t drop it.

Point the provider at a HookWatch endpoint and every delivery is captured. When a handler throws you can see the exact request and response and replay the delivery once it’s fixed.

Get started

Start debugging your webhooks.

Point one endpoint at HookWatch, capture a failure, and replay it once it’s fixed. Free during beta.