Notifications
Get backupd alerts for failed backups, failed restore tests, unreachable databases and storage limits on Slack, Discord, Microsoft Teams, Telegram, email or a signed webhook.
Notification channels tell you when something needs attention. Open Notifications (or Settings → Notifications) to manage them. Owners and admins can add, edit and remove channels. Everyone in the workspace can see them and their delivery log.
Add a channel
Click Add channel, choose a type, give it a Name, fill in its fields, and pick the events it should receive.
| Channel | What you need |
|---|---|
| Recipients: one or more addresses, separated by commas. | |
| Slack | Webhook URL from an incoming webhook: Slack app → Incoming Webhooks → Add New Webhook to Workspace. |
| Discord | Webhook URL: Channel settings → Integrations → Webhooks → Copy Webhook URL. |
| Microsoft Teams | Workflow webhook URL: in the channel, Workflows → "Post to a channel when a webhook request is received". |
| Telegram | Bot token from @BotFather, and the Chat ID: a numeric chat ID, or @channelname for a public channel. Add the bot to the chat first. |
| Custom webhook | Endpoint URL: any HTTPS endpoint. backupd sends signed JSON. |
Events
Events marked "on" are selected by default for a new channel.
| Event | Category | Default | When |
|---|---|---|---|
| Backup failed | Backups | On | A scheduled or manual backup did not complete. |
| Copy to destination failed | Backups | On | A copy to one of your own buckets failed. |
| Restore test failed | Backups | On | A restore test against a temporary database failed. |
| Database unreachable | Backups | On | An uptime check couldn't connect to a database. |
| Backup succeeded | Backups | Off | Every successful backup. Noisy on hourly schedules. |
| Storage at 80% | Billing | On | 80% of the plan's included storage is used. |
| Storage at 100% | Billing | On | All of the plan's included storage is used. |
| Spending cap reached | Billing | On | Extra storage reached your spending cap, and new backups are paused. |
| Plan changed | Billing | Off | The workspace plan was upgraded, downgraded or cancelled. |
| Member joined | Workspace | Off | Someone accepted an invitation. |
Billing alerts at 80% and 100% of included storage are also emailed to the workspace's owners and admins, whether or not you have set up a channel.
Manage channels
From the ⋯ menu next to a channel:
- Send test sends a Test notification to check the channel works.
- Edit events changes what the channel receives.
- Pause or enable the channel without deleting it.
- Remove the channel.
Delivery log
The Delivery log shows the last 30 deliveries and whether each was Delivered, Queued or Failed (with the number of attempts). Failed deliveries are retried with increasing delays for up to about an hour. A delivery that fails because the URL is wrong or the webhook was revoked is not retried. Fix the channel and send a test.
Webhook payloads
A Custom webhook receives a POST with a JSON body:
{
"id": "4f0c2a9e-…",
"type": "backup.failed",
"createdAt": "2026-10-16T09:00:12.000Z",
"organization": { "id": "…", "name": "Acme", "slug": "acme" },
"data": { "…": "…" },
"message": {
"title": "…",
"summary": "…",
"url": "https://…"
}
}Each request has these headers:
| Header | Value |
|---|---|
x-backupd-event | The event type, for example backup.failed. |
x-backupd-event-id | The event's ID. Use it to ignore duplicates. |
x-backupd-timestamp | Unix time in seconds when the request was signed. |
x-backupd-signature | t=<timestamp>,v1=<signature> |
Event types: backup.failed, backup.replication_failed, backup.verification_failed, backup.succeeded, database.unreachable, storage.threshold_80, storage.threshold_100, budget.cap_reached, subscription.changed, member.joined, and test for Send test.
Verify the signature
When you add a webhook channel, backupd shows a signing secret once. Save it. The signature is the hex HMAC-SHA256 of the timestamp, a dot, and the raw request body, using the signing secret:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, header, rawBody) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Reject requests whose timestamp is too old to protect against replays.