Testing

Test webhook routes with signed requests and no crypto code.

next-webhooks/testing builds correctly signed headers for every built-in provider, so route tests call your handler exactly the way the provider would.

tests/stripe-webhook.test.ts
import { expect, it } from "vitest";
import { stripeHeaders } from "next-webhooks/testing";
import { POST } from "../app/api/webhooks/stripe/route";

it("handles invoice.paid", async () => {
  const body = JSON.stringify({
    id: "evt_1",
    type: "invoice.paid",
    data: { object: {} },
  });
  const res = await POST(
    new Request("http://localhost/api/webhooks/stripe", {
      method: "POST",
      body,
      headers: await stripeHeaders({
        secret: process.env.STRIPE_WEBHOOK_SECRET!,
        body,
      }),
    }),
  );
  expect(res.status).toBe(200);
});

Available builders

  • stripeHeaders({ secret, body, timestamp? })
  • githubHeaders({ secret, body, deliveryId?, event? })
  • svixHeaders({ secret, body, id?, timestamp? })
  • slackHeaders({ secret, body, timestamp? })
  • paddleHeaders({ secret, body, timestamp? })
  • shopifyHeaders({ secret, body, topic?, webhookId? })
  • lemonsqueezyHeaders({ secret, body, event? })
  • vercelHeaders({ secret, body })
  • discordHeaders({ privateKey, body, timestamp? })
  • hmacHeaders({ secret, body, header, algorithm?, encoding?, prefix? })

Testing Discord routes

Discord verifies against a public key instead of a shared secret, so tests generate a key pair: configure the provider with the public half and sign requests with the private half.

tests/discord-webhook.test.ts
import { discordKeys, discordHeaders } from "next-webhooks/testing";
import { webhook, discord } from "next-webhooks";

const { publicKey, privateKey } = await discordKeys();
const POST = webhook({
  provider: discord({ publicKey }),
  handler: async () => {},
});

const body = JSON.stringify({ id: "1", type: 2 });
const res = await POST(
  new Request("http://localhost/api/webhooks/discord", {
    method: "POST",
    body,
    headers: await discordHeaders({ privateKey, body }),
  }),
);