Custom providers

Cover any HMAC-signed webhook with the generic hmac() provider.

Most providers sign the raw body with an HMAC and put it in one header. The hmac() provider covers that whole family: you pick the algorithm (sha1, sha256, sha512), the encoding (hex or base64), an optional prefix like sha256=, and where the event id and type travel:

app/api/webhooks/acme/route.ts
import { webhook, hmac } from "next-webhooks";

export const POST = webhook({
  provider: hmac({
    name: "acme",
    secret: process.env.ACME_WEBHOOK_SECRET!,
    header: "x-acme-signature",
    prefix: "sha256=",
    eventId: (headers) => headers.get("x-acme-delivery"),
    eventType: (_headers, payload) =>
      (payload as { type?: string } | null)?.type ?? null,
  }),
  handler: async (event) => {
    // ...
  },
});

Writing a provider from scratch

If a service uses a scheme hmac() cannot express, implement the WebhookProvider interface. verify receives the exact request bytes:

lib/my-provider.ts
import type { WebhookProvider } from "next-webhooks";

const myProvider: WebhookProvider = {
  name: "my-service",
  async verify(rawBody, headers) {
    // rawBody is a Uint8Array of the exact bytes that were signed
    const valid = await checkSignature(rawBody, headers);
    if (!valid) return { ok: false, reason: "Signature mismatch" };
    return { ok: true, id: headers.get("x-delivery-id"), type: null };
  },
};