Test Svix webhooks locally
Receive Svix-signed webhook events on localhost with a Horizon tunnel, and verify them with the svix npm package in a Next.js route.
Receive Svix webhooks on your laptop while you build, with a URL Svix can reach.
Svix delivers webhooks for many companies. You get a signing secret from the app portal of the service that sends you events. The steps below work for any sender that uses Svix.
Before you begin
- Node.js 18 or later
- A Horizon account and the CLI (see Getting started)
- A reserved subdomain for
-s. Reserve one on the Subdomains page. - Access to the Svix app portal of the service that sends you webhooks
- A Next.js App Router app
Start your app
Install the official svix package.
npm install svixSvix signs the message ID, the timestamp and the raw body with HMAC-SHA256 (a keyed hash). It sends the result in the svix-id, svix-timestamp and svix-signature headers. The Webhook class checks all three and throws when they don't match. Read the raw body with request.text(), because a parsed and re-serialized body breaks the signature.
import { Webhook } from "svix";
export async function POST(request: Request) {
const secret = process.env.SVIX_WEBHOOK_SECRET;
if (!secret) {
return new Response("Missing SVIX_WEBHOOK_SECRET", { status: 500 });
}
const body = await request.text();
const headers = {
"svix-id": request.headers.get("svix-id") ?? "",
"svix-timestamp": request.headers.get("svix-timestamp") ?? "",
"svix-signature": request.headers.get("svix-signature") ?? "",
};
try {
new Webhook(secret).verify(body, headers);
} catch {
return new Response("Invalid signature", { status: 400 });
}
const event = JSON.parse(body);
console.log(`Received Svix event: ${event.type}`);
return new Response("ok", { status: 200 });
}Svix rejects messages with a timestamp more than five minutes from the current time. The library applies that check for you.
Store the signing secret in an environment variable. It starts with whsec_.
SVIX_WEBHOOK_SECRET=whsec_replace-with-your-secretStart the app on port 3000.
npm run devStart a tunnel
Use -s with a subdomain you reserved. Without it, the subdomain is random and changes every run, so your Svix endpoint would point at a dead URL after a restart. Reserve it first on the Subdomains page. Reserved subdomains are a paid feature, see Pricing.
hrzn tunnel http://localhost:3000 -s my-appHORIZON: Tunnel connected
URL https://my-app.hrzn.run (reserved)
Forwarding http://localhost:3000
Request log https://hrzn.run/dashboard/tunnels/my-appYour public URL is https://my-app.hrzn.run. Keep this terminal open.
Add the endpoint in the Svix app portal
An endpoint is a URL you control plus the event types you want to receive.
- Open the app portal of the service that sends you webhooks.
- Add an endpoint.
- Enter
https://my-app.hrzn.run/api/webhooks/svixas the URL. - Choose the event types you want. If you choose none, the endpoint receives all events.
- Save the endpoint, then copy its signing secret into
SVIX_WEBHOOK_SECRET.
Restart npm run dev after you edit .env.local.
Send a test event
Svix gives every endpoint a Testing tab for example events.
- Open your endpoint in the app portal.
- Select the Testing tab.
- Send an example event for one of your event types.
- Select the message to see its payload and every delivery attempt.
Resend a message
Svix keeps past messages, so you can send one again without a new event.
- Find the message in the app portal.
- Open the options menu next to one of its attempts.
- Select resend.
To resend everything that failed, open the endpoint's details page and select Options, then Recover Failed Messages. Pick a time window.
Check it works
Send an example event from the Testing tab. Your Horizon terminal prints one line for it:
POST 200 /api/webhooks/svixYour app terminal prints the event type:
Received Svix event: <event type>The message in the app portal shows the attempt as succeeded.
Troubleshooting
The handler returns 400
- Check that
SVIX_WEBHOOK_SECRETis the signing secret of this endpoint, including thewhsec_prefix. Each endpoint has its own secret. - Verify the raw body from
request.text(). Don't runJSON.parseandJSON.stringifyfirst. - Restart
npm run devafter you edit.env.local. - Check your computer's clock. Svix rejects timestamps more than five minutes away.
The URL changed after a restart
You started the tunnel without -s, so Horizon gave you a new random subdomain. Restart with -s my-app and the URL stays the same. -s needs a subdomain you reserved, see Pricing.
Nothing reaches your app
- Check that the Horizon terminal is still running. If its last line is
Connection lost. Reconnecting…, wait forReconnected. - Check that the endpoint URL ends with
/api/webhooks/svix.
Next steps
- Read Svix's guide to verifying webhooks.
- Read Svix's guide to replaying messages.
Test MongoDB Atlas webhooks locally
Receive MongoDB Atlas alert webhooks on localhost with a Horizon tunnel, and verify the X-MMS-Signature header.
Test VMware Workspace ONE (Omnissa) webhooks locally
Receive Workspace ONE UEM event notifications on localhost with a Horizon tunnel, and check the credentials the console sends.