Channels are where alerts go. An admin adds them in Settings, then attaches them to one or more alert rules. Every channel has a Test button — use it before relying on it.
One digest per send, listing everything due, rather than one email per item. Somebody with forty expiring certificates reads one message.
Slack and Microsoft Teams
Create an incoming webhook in Slack or Teams and paste its URL. That URL is the credential — anyone holding it can post to your channel — so after saving we only ever show part of it back. To change it, delete the channel and add it again.
Webhooks
For your own systems. Each alert is posted individually as JSON, one event per fact. A signing secret is shown once, when you create the channel; copy it then.
Every request carries two headers: X-Signature-Timestamp, the Unix time in seconds, and X-Signature, an HMAC-SHA256 of the timestamp, a full stop and the raw body. Recompute it with your secret, compare in constant time, and reject timestamps more than a few minutes old.
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, timestamp, rawBody, signature) {
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
return age < 300 &&
expected.length === signature.length &&
timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}Delivery history
Admins can see every delivery attempt — pending, sent, failed or cancelled — with the error a failed attempt returned.