Test MongoDB Atlas webhooks locally
Receive MongoDB Atlas alert webhooks on localhost with a Horizon tunnel, and verify the X-MMS-Signature header.
Receive Atlas alert notifications on your laptop while you build, with a URL Atlas can reach.
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. - A MongoDB Atlas project where you can edit integrations and alert settings
Start your app
Atlas sends alerts as HTTP POST requests. If you set a webhook secret, Atlas adds the X-MMS-Signature header. It holds the Base64-encoded HMAC SHA-1 (a keyed hash) of the request body. The X-MMS-Event header holds the alert state, such as alert.open or alert.close.
import { createHmac, timingSafeEqual } from "node:crypto";
export async function POST(request: Request) {
const secret = process.env.ATLAS_WEBHOOK_SECRET;
if (!secret) {
return new Response("Missing ATLAS_WEBHOOK_SECRET", { status: 500 });
}
const body = await request.text();
const received = request.headers.get("x-mms-signature") ?? "";
const expected = createHmac("sha1", secret).update(body).digest("base64");
const receivedBuffer = Buffer.from(received);
const expectedBuffer = Buffer.from(expected);
const isValid =
receivedBuffer.length === expectedBuffer.length &&
timingSafeEqual(receivedBuffer, expectedBuffer);
if (!isValid) {
return new Response("Invalid signature", { status: 401 });
}
const state = request.headers.get("x-mms-event");
const alert = JSON.parse(body);
console.log(`Received Atlas ${state}: ${alert.eventTypeName}`);
return new Response("ok", { status: 200 });
}Atlas treats any status other than 2xx as a failure.
Pick a secret and store it in an environment variable. Use a random, high-entropy string.
ATLAS_WEBHOOK_SECRET=replace-with-a-long-random-stringStart 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 Atlas webhook would point at a dead URL after a restart. 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 webhook in Atlas
- Open Project Settings, then the Integrations tab.
- Under Webhook Settings, select Configure.
- Enter
https://my-app.hrzn.run/api/webhooks/mongodbas the Webhook URL. - Enter the same value as
ATLAS_WEBHOOK_SECRETin Webhook Secret. - Select Save.
Then add a webhook to an alert. When you create or edit an alert configuration, select the webhook as a notification option.
The Webhook Secret field is optional in Atlas. Without it, Atlas sends no X-MMS-Signature header and the handler above rejects every request.
Trigger an alert
Atlas has no button to send a test alert. MongoDB's docs suggest a temporary alert with a condition you can reach on purpose: a low disk space threshold on a test cluster, a connection count threshold you can cross by opening connections, or a replication lag threshold on a test replica set. Delete the alert configuration when you finish.
Check it works
When the temporary alert fires, Horizon prints one line for the request:
POST 200 /api/webhooks/mongodbYour app terminal prints the state and the event type name, for example:
Received Atlas alert.open: HOST_DOWNIf the line shows [401], see Troubleshooting.
Troubleshooting
The signature doesn't match
- Check that
ATLAS_WEBHOOK_SECRETis identical to Webhook Secret in Atlas, with no extra spaces or newline. - Compute the HMAC over the raw body. Don't run
JSON.parseandJSON.stringifyfirst, because that can change the bytes. - Restart
npm run devafter you edit.env.local. - If you changed Webhook Headers Template or Webhook Body Template, the body is no longer the default alert JSON. The handler's
JSON.parseandeventTypeNameread can fail.
Atlas emails you about the webhook
Atlas emails the project owner when the webhook URL or key becomes invalid, and can eventually remove the settings. Keep the tunnel running while you test, and use -s so the URL stays the same.
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 Webhook URL ends with
/api/webhooks/mongodb. - Check that the alert configuration lists the webhook as a notification.
Next steps
- Read MongoDB's guide to integrating with webhooks.
Test HostedHooks webhooks locally
Receive HostedHooks webhook events on localhost with a Horizon tunnel, and verify the HostedHooks signature in a Next.js route.
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.