Notification channels
Getting alerts out by email or signed webhook, what a channel can subscribe to, and how to verify a webhook signature.
A channel is where alerts go. Two kinds: EMAIL and WEBHOOK.
| Field | |
|---|---|
name | 1–64 characters. |
kind | EMAIL or WEBHOOK. |
target | An address, or an http:///https:// URL up to 512 characters. |
kinds | Which alert kinds to receive. Empty means every kind. |
isEnabled | |
hasSecret | Whether a signing secret is set. The secret itself is never returned. |
lastDeliveredAt, lastError | The receiver's own message, shown on the channel's page. |
Why the URL scheme is restricted
An outbound webhook is a fetch this platform makes on an operator's behalf.
Allowing any scheme would hand something like file: to whatever the delivery
client is willing to open, so the set of things it may fetch belongs in the
contract.
Webhook signatures
POST /notification-channels returns a signing secret once. No other response can carry
it.
Each delivery is signed HMAC-SHA-256 over <timestamp>.<body>. Verify it by recomputing
the digest over the timestamp and the raw body — not the parsed one, since re-serialising JSON
changes the bytes.
import { createHmac, timingSafeEqual } from 'node:crypto'
function verify(secret, timestamp, rawBody, signature) {
const expected = createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex')
const a = Buffer.from(expected)
const b = Buffer.from(signature)
return a.length === b.length && timingSafeEqual(a, b)
}Reject a timestamp far from now, or a replayed delivery is indistinguishable from a real one.
Email needs a transport. Set EMAIL_TRANSPORT=smtp and the SMTP settings in
configuration.
With no transport configured, email is skipped entirely and says so in the API log rather than failing. Alerts still open and still go out on webhooks. That is deliberate — a platform that refuses to boot because nobody configured a mail relay is worse than one that tells you.
Send only from a domain you control and have authenticated with SPF, DKIM, and DMARC. The
domain in EMAIL_FROM_ADDRESS must align with the DKIM signing domain, or DMARC fails even
when both records exist.
When nothing arrives
Check the portal. If the alert is not there, the condition was never noticed and the delivery side is not the problem.
Check the channel's lastError. It is the receiver's own message,
verbatim.
Check Redis. Delivery runs through the notify queue. If Redis is down,
alerts are raised and stored but nothing is sent.