Horizon

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.

app/api/webhooks/mongodb/route.ts
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.

.env.local
ATLAS_WEBHOOK_SECRET=replace-with-a-long-random-string

Start the app on port 3000.

npm run dev

Start 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-app
Output
HORIZON: Tunnel connected
  URL          https://my-app.hrzn.run (reserved)
  Forwarding   http://localhost:3000
  Request log  https://hrzn.run/dashboard/tunnels/my-app

Your public URL is https://my-app.hrzn.run. Keep this terminal open.

Add the webhook in Atlas

  1. Open Project Settings, then the Integrations tab.
  2. Under Webhook Settings, select Configure.
  3. Enter https://my-app.hrzn.run/api/webhooks/mongodb as the Webhook URL.
  4. Enter the same value as ATLAS_WEBHOOK_SECRET in Webhook Secret.
  5. Select Save.

Then add a webhook to an alert. When you create or edit an alert configuration, select the webhook as a notification option.

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:

Output
  POST    200  /api/webhooks/mongodb

Your app terminal prints the state and the event type name, for example:

Output
Received Atlas alert.open: HOST_DOWN

If the line shows [401], see Troubleshooting.

Troubleshooting

The signature doesn't match

  • Check that ATLAS_WEBHOOK_SECRET is identical to Webhook Secret in Atlas, with no extra spaces or newline.
  • Compute the HMAC over the raw body. Don't run JSON.parse and JSON.stringify first, because that can change the bytes.
  • Restart npm run dev after 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.parse and eventTypeName read 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 for Reconnected.
  • Check that the Webhook URL ends with /api/webhooks/mongodb.
  • Check that the alert configuration lists the webhook as a notification.

Next steps

On this page