Appearance
Notifications
When the server raises an alert, it writes one event and then delivers it. The event always lands in the system owner's inbox. From there it goes to push notifications on the owner's devices, and to every Slack channel and webhook set up by the people who should hear about that system. How status works covers when an alert is raised. This page covers where it goes.
The inbox
The INBOX page is the full feed of alerts for systems you own, newest first, grouped by day. Filter chips narrow it to critical or warning alerts. Click a row to open the event and its system. That marks it read. mark all read clears the unread count.
Read and resolved are separate. Read means you have seen the alert. Resolved means the problem went away, and only the server sets it, when the service recovers. Marking an alert read does not close it and does not re-arm it. See One open alert at a time.
The inbox gets every alert, at every severity, whatever your channel settings say. The severity floor described below applies to Slack and webhooks only.
Each system page also lists the events for that system, open ones first. Anyone who can see the system can read that list, except people who only received it through a group share. So people you share a system with see its events there even though they have no inbox copy.
Slack
Slack is connected per user, under Settings, Notifications.
Connect a workspace
- Click Add to Slack. daemonitor sends you through Slack's OAuth v2 install.
- Approve the app in your workspace. Slack returns you to the settings page with "Slack workspace connected."
- Click Choose a channel and pick where alerts go.
- Click Send test. A test message confirms the bot can post there.
The app asks for these bot scopes:
| Scope | Why |
|---|---|
chat:write | Post alerts. |
chat:write.public | Post to a public channel without being invited first. |
channels:read | List public channels for the picker. |
groups:read | List private channels the bot belongs to. |
A private channel only appears in the picker once the bot is a member. Run /invite @daemonitor in the channel, then reopen the picker.
Each message has the alert title and reason, and an Open in daemonitor button that links to the event's own page.
To stop Slack delivery, click Disconnect. That deletes the stored bot token. The app stays installed in your workspace until a workspace admin removes it there.
Severity floor
Each connected channel has a minimum level, set with the dropdown under the channel.
| Setting | min_level | Delivers |
|---|---|---|
| Critical only | error | errors |
| Critical + warnings | warning | errors and warnings. The default. |
| Everything | info | all alerts |
An alert below the floor still reaches the inbox. It just isn't posted.
When the Slack button is missing
If the server has no Slack app configured, the settings page says so instead of showing Add to Slack. Self-hosted servers need SLACK_CLIENT_ID and SLACK_CLIENT_SECRET from the Slack app's Basic Information page, and /api/slack/callback registered as a redirect URL.
Webhooks
A webhook sends a signed JSON POST to a URL you run, for every alert raised and every alert resolved. Use it to open a ticket, page through another tool, or hand the alert to an agent that can act on it.
Add one
Under Settings, Notifications, Webhooks, enter the URL, pick a severity floor and choose Add webhook. The page then shows the signing secret, which starts with whsec_. Copy it then. It is shown once and can't be retrieved later. To get a new one, remove the webhook and add it again.
The URL must use https and point at a public host. daemonitor refuses localhost, internal names such as *.local or *.internal, and private, loopback or link-local IP addresses, because the server is the one making the request. Redirects are not followed.
Send test posts a test event, so you can check the receiver before a real alert depends on it.
Events
X-Daemonitor-Event | Sent when |
|---|---|
alert.raised | An alert is raised and passes the severity floor. Not sent for a recurrence inside the 30-minute cooldown. |
alert.resolved | The condition behind an alert cleared. Carries the same id as its alert.raised. Also sent for an alert whose raise the cooldown held back, so expect an occasional id you haven't seen. |
test | You chose Send test. |
Slack only gets alert.raised. Webhooks get the resolution too, so whatever acted on an alert can close it.
Payload
json
{
"event": "alert.raised",
"id": "1dbaab1a-a74c-4a03-a029-6b847a0701d5",
"level": "error",
"title": "web-01: checkout unreachable",
"message": "connect ECONNREFUSED",
"source": "web",
"sourceDetail": "web:down:checkout",
"system": { "id": "db897ad6-…", "name": "web-01" },
"service": "web-checkout",
"detail": { "Administrators before": "4", "Administrators after": "5" },
"url": "https://daemonitor.com/systems/db897ad6-…/events/1dbaab1a-…",
"at": "2026-10-14T21:30:00.000Z"
}| Field | Meaning |
|---|---|
id | The alert's id. Use it to match an alert.resolved to its alert.raised. |
level | error, warning or info. |
source, sourceDetail | The plugin that raised it and the specific finding, for example web and web:down:checkout. |
service | The service's id on its host, when the alert came from one service. |
detail | Before and after values for findings that have them, such as a WordPress administrator change. Often absent. |
url | The alert's page in the dashboard. |
A test event has only event, level, title, message, url and at.
Verify the signature
Every request carries X-Daemonitor-Signature: t=<unix seconds>,v1=<hex>. v1 is an HMAC-SHA256 of <t>.<raw body>, keyed with your secret. Check it against the raw body before parsing it, and reject old timestamps to stop replays.
js
import { createHmac, timingSafeEqual } from "node:crypto"
export function verify(rawBody, header, secret, toleranceSeconds = 300) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || "")
if (!m) return false
if (Math.abs(Date.now() / 1000 - Number(m[1])) > toleranceSeconds) return false
const expected = createHmac("sha256", secret).update(`${m[1]}.${rawBody}`).digest("hex")
return timingSafeEqual(Buffer.from(expected), Buffer.from(m[2]))
}Delivery
daemonitor waits 5 seconds for a 2xx response. A timeout, an error status or a refused connection is logged and the delivery is dropped. There are no retries yet, so a receiver that is down misses that alert, though it still reaches the inbox. Deliveries go out one after another, so a slow receiver delays the next channel.
Push notifications
Push works in the daemonitor mobile apps for iOS and Android, and in browsers that support web push.
Turn it on per device under Settings, Notifications. The browser or phone asks for permission, and the device then registers with the server.Push goes to every registered device of the system's owner, at every severity. It has no severity floor of its own. Alerts that recur inside the 30-minute cooldown are not pushed.
Self-hosted servers need a Firebase service account in FCM_SERVICE_ACCOUNT, or the same JSON base64-encoded in FCM_SERVICE_ACCOUNT_BASE64. Without it, push is off and nothing else changes.
Who gets paged under sharing
When you share systems, alerts for a system reach:
- The system's owner.
- Anyone the owner gave a manager grant on the whole account.
- Anyone the owner gave a manager grant on that system.
Viewers are not paged. A viewer was given a window onto the system. A manager was trusted to act on it, and an alert asks for action.
People who received a system through a group share are not paged either. A group share hands over specific services, and a system-level alert can name any service on the host, including ones the grantee can't otherwise see.
Members of an organization the system belongs to are not paged.
If an owner and their manager both connect Slack to the same channel, the message is posted once. The same goes for two webhooks with the same URL.
Managers get the Slack message or webhook, but the alert does not appear in their own inbox, because the inbox belongs to the owner. They see the event on the system page. Push notifications go to the owner's devices only.
Not implemented yet
- Email delivery. The email row under Settings, Notifications is a switch stored in your browser. It sends nothing.
- Quiet hours. The quiet hours setting is also stored in your browser only. The server ignores it, and alerts arrive at any hour.