Skip to Content

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

FieldNotes
Webhook URLMust match https://discord.com/api/webhooks/<id>/<token> (also accepts discordapp.com, canary., ptb.). Encrypted at rest.
Display nameOptional. Overrides the webhook’s saved username. ≤ 80 characters.
Avatar URLOptional. 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:

  1. Open the destination text channel → Edit ChannelIntegrationsWebhooks.
  2. New Webhook (or pick an existing one).
  3. 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.

Add a Discord channel

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:

SeverityHexDecimal
critical#e74c3c15158332
warning#f1c40f15844367
success#2ecc713066993
info#3498db3447003

Title emojis

StateEmoji
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 responseCaptured as
Non-2xxfailure_reason = Discord responded HTTP <code> · <body>
429 Too Many RequestsSame, 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.

What’s next

Last updated on