# Notifications

Hear about what matters in Slack, Discord, your own webhook or by email. Admins set them up under **Settings → Notifications**. These are the things the platform does; to hear that an application itself is failing requests or answering slowly, see [alerts](/docs/alerts).

## Events

| Event | When |
| --- | --- |
| A deployment failed | A build or start failed, with the error. |
| A deployment went live | A new version runs. |
| An agent changed something | A coding agent deployed, changed settings, and so on. |
| An agent's change waits for approval | See [approvals](/docs/agent-safety). |
| A server went offline (and came back) | Once when it goes quiet, and again when it's back. |
| A scheduled snapshot of a volume failed | A [backup](/docs/backups) on a schedule didn't happen, with the reason. |

## Without a channel

Some things mustn't go unheard: a service that ran out of memory or is close to it, a volume that filled up and stopped its service, a scheduled snapshot that failed, and the month reaching its spending limit. While no channel listens for one of these, the organization's owners get it by email. Once a channel does, it goes there instead.

## Channels

- **Slack**: an [incoming webhook](https://api.slack.com/messaging/webhooks) URL (`https://hooks.slack.com/…`).
- **Discord**: a channel webhook URL (Channel settings → Integrations → Webhooks).
- **Webhook**: any HTTPS URL; Alf POSTs JSON.
- **Email**: when it's switched on for the platform.

**Send a test** checks a channel. If a delivery fails, the channel shows the last error.

## Webhook format

```json
{
  "event": "deploy_failed",
  "title": "Deploy of shop / web failed",
  "text": "pull access denied (…)",
  "url": "https://runalf.com/app/projects/…",
  "data": { "deployment_id": "dep_…", "service_id": "svc_…" },
  "organization_id": "org_…",
  "sent_at": "2026-10-10T12:00:00.000Z"
}
```

Each delivery has `X-Smartalfred-Event` and `X-Smartalfred-Signature: sha256=…`, an HMAC-SHA256 of the body with the channel's signing secret (shown once, when you add it). Check it before trusting a delivery:

```js
const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
```
