Documentation menu

Alerts

When an incident opens, Asniher sends a down alert to every alert channel connected to the monitor. When the incident closes, it sends a recovery alert with the total downtime. That is two notifications per incident, never a stream of repeats.

Channels by plan

Channel Free Pro Team
Email Yes Yes Yes
Slack Yes Yes Yes
Discord Yes Yes Yes
Telegram — Yes Yes
Webhook — Yes Yes

You can send a test notification after adding a channel to confirm it works. Test notifications arrive as a down alert for a monitor named "Test alert from Asniher".

Email

Alerts are sent from alerts@asniher.com. Add this address to your allowlist if your provider filters aggressively.

  • Subject: [DOWN] <monitor name> or [UP] <monitor name>.
  • Down alerts include the target, the failure reason and the detection time.
  • Recovery alerts include the target, the downtime and the root cause if you recorded one.
  • Every email links to the incident in your dashboard.

Slack

  1. In Slack, create an app with Incoming Webhooks enabled, or use an existing one, and add a webhook to the channel you want.
  2. Copy the webhook URL. It starts with https://hooks.slack.com/services/.
  3. In Asniher, add a Slack channel and paste the URL.

Messages show the monitor name, target, reason and a View incident button.

Discord

  1. In Discord, open Server Settings → Integrations → Webhooks and create a webhook for the channel.
  2. Copy the webhook URL.
  3. In Asniher, add a Discord channel and paste the URL.

Messages arrive as an embed coloured red for down and green for recovery.

Telegram

Available on Pro and Team.

  1. Add the Asniher bot to the group that should receive alerts, or start a direct conversation with it.
  2. Enter the numeric chat ID of that chat in Asniher. Group chat IDs are negative numbers, for example -1001234567890.

Webhook

Available on Pro and Team. Asniher sends a POST request with a JSON body to your URL. Any response with a status code of 300 or above is treated as a failed delivery, so return 2xx once you have accepted the event.

Headers

Header Value
content-type application/json
user-agent Asniher-Alerts/1.0
x-asniher-event monitor.down or monitor.up
x-asniher-timestamp Unix time in seconds when the request was signed
x-asniher-signature Hex HMAC-SHA256 signature. Sent only when the channel has a secret.

Payload

{
  "event": "monitor.down",
  "monitor": {
    "id": "6f1c2a9e-...",
    "name": "Checkout API",
    "target": "https://api.example.com/healthz",
    "type": "http"
  },
  "incident": {
    "id": "b3d8e0f4-...",
    "startedAt": "2026-10-14T09:12:30.000Z",
    "resolvedAt": null,
    "cause": "Unexpected status 503 (expected 200-299)",
    "url": "https://app.asniher.com/dashboard/incidents/b3d8e0f4-..."
  },
  "sentAt": "2026-10-14T09:12:31.204Z"
}

For monitor.up, resolvedAt is set. monitor.type is one of http, keyword, ssl or heartbeat. Test notifications use the monitor ID test.

Verifying the signature

Set a secret on the webhook channel. Asniher computes:

x-asniher-signature = hex( HMAC-SHA256( secret, x-asniher-timestamp + "." + raw request body ) )

To verify:

  1. Read the raw request body as a string. Do not parse and re-serialize the JSON first; whitespace or key order changes break the signature.
  2. Compute the HMAC over <timestamp>.<raw body> with your secret.
  3. Compare with x-asniher-signature using a constant-time comparison.
  4. Reject requests whose timestamp is more than 5 minutes from your clock, to block replays.

Node.js, no dependencies:

import { createHmac, timingSafeEqual } from "node:crypto";
import { createServer } from "node:http";

const SECRET = process.env.ASNIHER_WEBHOOK_SECRET;

function verify(rawBody, timestamp, signature) {
  if (!timestamp || !signature) return false;
  const age = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!Number.isFinite(age) || age > 300) return false;
  const expected = createHmac("sha256", SECRET).update(`${timestamp}.${rawBody}`).digest("hex");
  if (expected.length !== signature.length) return false;
  return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(signature, "hex"));
}

createServer((req, res) => {
  let raw = "";
  req.setEncoding("utf8");
  req.on("data", (chunk) => (raw += chunk));
  req.on("end", () => {
    const ok = verify(raw, req.headers["x-asniher-timestamp"], req.headers["x-asniher-signature"]);
    if (!ok) {
      res.writeHead(401).end();
      return;
    }
    const event = JSON.parse(raw);
    console.log(event.event, event.monitor.name, event.incident.cause);
    res.writeHead(204).end();
  });
}).listen(3000);

With Express, use express.raw({ type: "application/json" }) on the webhook route and pass req.body.toString("utf8") as the raw body.

Who gets alerted

Alerts go to the channels attached to each monitor. Attach different channels to different monitors to route, for example, staging alerts to a quiet channel and production alerts to on-call.

Questions about this page? Email support@asniher.com.