Discord
WatchDeck delivers Discord notifications via channel webhooks . One webhook URL = one Discord channel. Messages render as severity-coloured embeds with structured fields.
Required fields
| Field | Notes |
|---|---|
| Webhook URL | Must match https://discord.com/api/webhooks/<id>/<token> (also accepts discordapp.com, canary., ptb.). Encrypted at rest. |
| Display name | Optional. Overrides the webhook’s saved username. ≤ 80 characters. |
| Avatar URL | Optional. HTTP(S) image URL — overrides the webhook’s saved avatar. |
The URL is encrypted at rest (AES-256-GCM) and never echoed back to the browser. Display name and avatar are stored in plaintext.
Where the webhook URL comes from
In Discord:
- Open the destination text channel → Edit Channel → Integrations → Webhooks.
- New Webhook (or pick an existing one).
- Copy the Webhook URL — that’s what WatchDeck needs.
The URL implicitly carries the channel. To post in a different channel, generate a new webhook there.
Message format
Each dispatch sends a single embed. Generic title/summary fields are intentionally ignored for incident kinds — the embed is rebuilt from structured fields so it renders consistently regardless of cause:
{
"embeds": [
{
"title": "🔴 Production API — DOWN",
"description": "[**Production API**](https://app.watchdeck.dev/incidents/abc123) · [Open endpoint ↗](https://api.example.com/health)\n\nHTTP 502 — expected 200",
"color": 15158332,
"timestamp": "2026-05-01T14:32:00.000Z",
"fields": [
{ "name": "Cause", "value": "Endpoint down", "inline": true },
{ "name": "Started", "value": "2026-05-01 14:30 UTC", "inline": true }
],
"footer": { "text": "WatchDeck · #abcdef7" }
}
],
"username": "WatchDeck Ops",
"avatar_url": "https://example.com/logo.png"
}For resolved incidents, an additional Duration field appears.
Severity colours
Discord embeds use 24-bit integer colours. WatchDeck’s palette:
| Severity | Hex | Decimal |
|---|---|---|
| critical | #e74c3c | 15158332 |
| warning | #f1c40f | 15844367 |
| success | #2ecc71 | 3066993 |
| info | #3498db | 3447003 |
Title emojis
| State | Emoji |
|---|---|
| down | 🔴 |
| degraded | 🟡 |
| recovered | ✅ |
| escalated | 🚨 |
| test | 🧪 |
| info | ℹ️ |
Test send
Click Test on the channel card to fire a synthetic channel_test embed into the channel. The result is shown inline on the card and recorded in the Delivery log with kind='channel_test'.
A successful test sets is_connected=true and updates last_success_at; a failed test does the opposite.
Failure handling
The dispatcher considers any 2xx response a success. Discord returns 204 No Content for accepted webhook deliveries.
| Discord response | Captured as |
|---|---|
| Non-2xx | failure_reason = Discord responded HTTP <code> · <body> |
| 429 Too Many Requests | Same, plus retry_after from the JSON body or header captured into provider_meta |
| Network / timeout (10s) | failure_reason = the underlying error message |
Discord’s response also includes an x-discord-messageid header — WatchDeck stores it on the log row’s provider_id, so you can correlate a delivery row back to a Discord message id.
What’s not in the Discord channel today
- No mention helpers. WatchDeck does not auto-prepend
@everyone,@here, or<@id>to messages. If you want a mention, use a webhook channel and craft the body yourself. - No threading. Each incident kind is a top-level message in the channel. There’s no thread linking.
- No interactive components. No buttons, no slash-command-style actions.
- No app token / OAuth path. Webhook only.
Troubleshooting
- 404 on Test — the webhook was deleted in Discord (or the channel it lives in was). Generate a new one.
- 401 Unauthorized — the URL token portion has been rotated. Discord rotates webhook tokens silently in some integrity-violation cases — re-copy the URL.
- Avatar / username don’t apply — your server’s role permissions for the webhook may forbid overriding. Check the integration’s role allow-list.
- Embed looks crowded — the embed is intentionally compact. If you need a different layout, use a webhook channel and own the body.
Quotas and limits
- Counts toward your per-plan channel cap — see Notifications → Quotas.
- Discord rate-limits webhooks at roughly 5 requests per 2 seconds per webhook (server-wide limits also apply). 429s come back with
retry_after; WatchDeck logs them but doesn’t currently auto-retry.