Reference
Notifications
Notification channels (email, Slack, Teams, Telegram, ntfy, webhooks), routing events to them, digests, rate limits and the webhook format.
Configure → Notifications has the channels, the routing of events to them, and recent messages. Admins change it.
Channels
Section titled “Channels”| Type | Settings |
|---|---|
| SMTP host, port, encryption (none, STARTTLS, or implicit TLS), username and password (empty for none), from address, recipients (comma-separated). Messages are HTML with a plain-text part; attack alerts include a chart of the target’s traffic | |
| Slack | An incoming webhook URL. Mattermost and other Slack-compatible webhooks work too |
| Microsoft Teams | A Workflows webhook URL. Messages are Adaptive Cards with an “Open in Heimnull” button |
| Telegram | A bot token, and chat IDs (comma-separated, for example -1001234567890 or @channelname) |
| ntfy | Server (empty for https://ntfy.sh), topic, and an optional access token. On ntfy.sh anyone who knows a topic can subscribe: use a hard-to-guess name, or a token |
| Webhook | A URL that receives a JSON POST per message, and an optional signing key |
Every channel has Messages per minute (default 20) and Enabled. Webhook URLs, tokens and passwords are stored encrypted, and errors never show a URL, since for Slack and Teams the URL is the secret.
Send test sends a sample attack alert through the channel, so you see the real layout. Preview emails opens every email template with a made-up event.
Events
Section titled “Events”| Event | When |
|---|---|
attack_detected |
An attack started (or an observed anomaly became an attack) |
attack_ended |
An attack ended |
attack_observed |
A baseline anomaly in observe mode. Not routed anywhere until you route it |
mitigation_applied |
A blackhole or FlowSpec rule was announced, with what each router received |
mitigation_extended |
A mitigation was extended |
mitigation_withdrawn |
A mitigation was withdrawn or expired |
mitigation_refused |
A mitigation was refused by the safety checks, the cap on active mitigations or the emergency withdraw-all |
bgp_session |
A BGP session went up or down |
exporter_silent |
An exporter stopped sending (after its “silent after” time), and when it recovers |
ingest_problem |
Drops or ClickHouse insert failures, at most every 10 minutes while they last, and a recovery |
dataset_failed |
A dataset couldn’t be refreshed |
system_health |
Heimnull’s own health: disk, ClickHouse memory and merges, an outdated compose file or ClickHouse configuration, SNMP failing. See System |
Routing
Section titled “Routing”Routing maps events to channels, globally or per hostgroup. For an event in a hostgroup that has its own routes, those replace the global ones; events with no hostgroup (BGP, exporters, ingest, datasets, health) use the global routes.
Digests and rate limits
Section titled “Digests and rate limits”- Coalescing. The first event of a kind goes out at once. Further events with the same key within the coalescing window (Settings → Notifications, default 30 seconds) wait and go as one digest. Attacks are grouped per hostgroup and direction, mitigations per hostgroup, and BGP, exporter, ingest and dataset events each by kind.
- Rate limit. Each channel sends at most its messages per minute; when more are waiting, the last message carries the rest as one digest.
- Retries. A failed send is retried with growing delays (10 seconds, doubling, up to 10 minutes between tries) and given up after 8 attempts. Delivery is at least once: a restart at the wrong moment can repeat a message.
Base URL and server name
Section titled “Base URL and server name”Under Settings → Notifications:
- Base URL: where the links in messages point, for example
https://heimnull.example.net. Each message links to its attack, mitigation or host page. - Server name: put into every subject, as in
Heimnull (Dallas): …. Empty uses the base URL’s host. It tells several Heimnull servers apart in one chat.
Webhook format
Section titled “Webhook format”Each message is a POST with a JSON body:
{ "source": "heimnull", "subject": "Heimnull (Dallas): Attack detected: 203.0.113.10 (DNS amplification, 4.2 Gbps)", "text": "…the plain-text message…", "link": "https://heimnull.example.net/attacks/1234", "events": [ { "type": "attack_detected", "at": "2026-10-19T14:02:11Z", "data": { "…": "the event's details" } } ]}events holds one entry per event in the message (several in a digest), each with its type, time and details.
With a signing key, each request carries X-Heimnull-Timestamp and X-Heimnull-Signature: sha256=<hex>, the HMAC-SHA256 with your key of the timestamp, a ., and the raw body. To verify, compute the same over what you received, compare in constant time, and reject old timestamps.