Skip to main content

Uptime Kuma Discord Webhook: Grafana and Alertmanager Alerts

Send server monitoring alerts to Discord: Uptime Kuma discord webhook setup, Grafana contact point, Alertmanager discord_configs and a cron health check.

7 min read Also available in Русский
On this page

Why monitoring alerts belong in Discord

An outage that lands in #alerts is seen in seconds; the same outage in an inbox is seen after lunch. Webhooks make this cheap: no bot, no OAuth, no server-side code. Create one in Server Settings → Integrations → Webhooks, copy the URL, and every tool below can POST to it.

Four setups, in order of effort:

  • Uptime Kuma — a native Discord notification type; paste the URL and you are done.
  • Grafana Alerting — a contact point of type Discord.
  • Prometheus Alertmanager — the discord_configs receiver, available since Alertmanager 0.25.
  • A cron job — a short bash script for the box that runs none of the above.

Then colour-coding, so a red card means “down” at a glance, and storm control, so one bad night does not turn into four hundred pings.

Use a dedicated webhook for the alert channel (how to get the URL) and treat it like a password: anyone holding the URL can post, and the only fix is deleting and recreating it.

Uptime Kuma Discord webhook

Uptime Kuma has Discord as a first-class notification type; no generic webhook mapping needed.

  1. Open Settings → Notifications → Setup Notification.
  2. Set Notification Type to Discord.
  3. Paste the URL into Discord Webhook URL.
  4. Optionally set Bot Display Name (this becomes the username override, so it cannot contain “discord” or “clyde”) and a Prefix Custom Message.
  5. Click Test, then Save. Tick Apply on all existing monitors, or attach it per monitor on the monitor’s edit page.

The card itself is fixed: on DOWN, a red embed titled ”❌ Your service name went down. ❌” with Service Name, Service URL, Time and Error fields; on UP, a green embed with a Ping field instead of the error. You write no JSON; the display name and the prefix are the only styling knobs.

The prefix is where a role ping goes. Uptime Kuma sends it as the message content, so <@&ROLE_ID> there mentions the on-call role above the embed. If it renders as plain text, the id is wrong (copy it with Developer Mode → right-click the role → Copy Role ID); how allowed_mentions decides who gets pinged is in mentions in webhooks.

Storm control lives on each monitor: Retries (consecutive failed checks before the monitor flips to DOWN), Heartbeat Retry Interval, and Resend Notification if Down X times consecutively (leave it at 0 for exactly one DOWN message).

Grafana Discord webhook (contact point)

Grafana Alerting ships a Discord integration:

  1. Alerting → Contact points → Create contact point.
  2. Integration: Discord. Paste the webhook URL.
  3. Keep Title and Message content on their defaults ({{ template "default.title" . }} and {{ template "default.message" . }}).
  4. Test, then Save.
  5. Alerting → Notification policies: point the default policy, or a child policy with label matchers, at this contact point. Otherwise the contact point exists but nothing is routed to it.

Grafana sends the message as the text above an embed; the embed is red while the group is firing and green on resolve, and carries the title and a link back to Grafana. Title and message are trimmed to Discord’s limits (256 and 2000 characters), so a rule with thirty labels will not produce a 400.

As provisioning YAML:

# provisioning/alerting/discord.yaml
apiVersion: 1
contactPoints:
  - orgId: 1
    name: discord-ops
    receivers:
      - uid: discord-ops
        type: discord
        settings:
          url: https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN
          title: '{{ template "default.title" . }}'
          message: '{{ template "default.message" . }}'
          use_discord_username: false
policies:
  - orgId: 1
    receiver: discord-ops
    group_by: ['alertname', 'instance']
    group_wait: 30s
    group_interval: 5m
    repeat_interval: 4h

The three timing fields are your storm control: group_wait bundles alerts arriving in the first 30 seconds into one message, group_interval caps how often that group sends updates, and repeat_interval sets how often a still-firing alert is re-sent. With a four-hour repeat, a weekend outage produces a handful of messages, not hundreds.

Alertmanager Discord receiver

Since Alertmanager 0.25 the Discord integration is built in; older releases needed a bridge or a generic webhook receiver behind a payload-reshaping proxy, and upgrading is the shorter path.

# alertmanager.yml
global:
  resolve_timeout: 5m

route:
  receiver: discord
  group_by: ['alertname', 'job']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    - matchers:
        - severity = critical
      receiver: discord-critical
      repeat_interval: 1h

receivers:
  - name: discord
    discord_configs:
      - webhook_url: https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN
        send_resolved: true

  - name: discord-critical
    discord_configs:
      - webhook_url: https://discord.com/api/webhooks/OTHER_ID/OTHER_TOKEN
        send_resolved: true
        title: '{{ template "discord.default.title" . }}'
        message: '{{ template "discord.default.message" . }}'

inhibit_rules:
  - source_matchers:
      - severity = critical
    target_matchers:
      - severity = warning
    equal: ['alertname', 'instance']

Alertmanager sends one embed per notification, red for firing and green for resolved, description rendered from message and cut to the embed limit. Validate and reload:

amtool check-config alertmanager.yml
curl -X POST http://localhost:9093/-/reload

Keep webhook_url out of git (newer releases accept webhook_url_file; check the docs for your version), and remember that for: in the Prometheus rule is the first line of defence: with for: 5m nothing reaches Alertmanager until the condition has held for five minutes.

A cron health check in bash

For a VPS with no monitoring stack, a script that runs every minute and posts only on state change:

#!/usr/bin/env bash
# /usr/local/bin/healthcheck.sh
set -u

WEBHOOK_URL="${DISCORD_WEBHOOK_URL:?set DISCORD_WEBHOOK_URL}"
TARGET="https://example.com/health"
STATE_FILE="/var/tmp/healthcheck.state"
FAIL_THRESHOLD=3

# curl prints 000 when the connection itself fails
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$TARGET")
prev=$(cat "$STATE_FILE" 2>/dev/null || echo "up:0")
prev_status=${prev%%:*}
fails=${prev##*:}

if [[ "$code" == "200" ]]; then
  status=up; fails=0
else
  status=down; fails=$((fails + 1))
  # not enough consecutive failures yet: keep the old state, stay quiet
  [[ $fails -lt $FAIL_THRESHOLD ]] && status=$prev_status
fi

echo "$status:$fails" > "$STATE_FILE"
[[ "$status" == "$prev_status" ]] && exit 0

if [[ "$status" == "down" ]]; then
  color=15548997; title="🔴 $TARGET is DOWN"
else
  color=5763719;  title="🟢 $TARGET is back UP"
fi

now=$(date -u +%Y-%m-%dT%H:%M:%SZ)
curl -s -o /dev/null -w 'discord: %{http_code}\n' --max-time 10 \
  -H "Content-Type: application/json" \
  -d @- "$WEBHOOK_URL" <<EOF
{
  "embeds": [{
    "title": "$title",
    "color": $color,
    "fields": [
      {"name": "HTTP status", "value": "$code", "inline": true},
      {"name": "Checked from", "value": "$(hostname)", "inline": true}
    ],
    "timestamp": "$now"
  }]
}
EOF

The state file holds up:0 or down:N. Three consecutive non-200 answers flip it to down and send one red card; the first 200 afterwards sends one green card; everything in between is silent. Install with chmod +x and a crontab entry:

DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN
* * * * * /usr/local/bin/healthcheck.sh >> /var/log/healthcheck.log 2>&1

To have the recovery update the red card instead of adding a green one, send the DOWN message with ?wait=true, keep the returned id in the state file and PATCH /messages/{id} when the service is back (editing and deleting webhook messages).

Colour-coded embeds for up and down

Keep the palette tiny and never change what each colour means:

StateColourcolor value
Down, firingred #ED424515548997
Up, resolvedgreen #57F2875763719
Degraded, warningyellow #FEE75C16705372
Maintenance, infoblurple #5865F25793266

Rules for every tool:

  • color is a decimal integer. A hex string such as "#ED4245" is rejected with a 400.
  • Put the state in the title too: push notifications show text only, and not everyone distinguishes red from green.
  • Send timestamp in ISO 8601 UTC; Discord renders it in each reader’s timezone.
  • Stay inside the embed limits when you build the payload yourself (256 characters for the title, 4096 for the description, 25 fields); Grafana and Alertmanager trim for you, your bash script does not.
  • More decimal values are in the embed colours guide.

Avoiding alert storms

A channel that fires constantly gets muted, and a muted channel is worse than none.

  1. Never alert on one failed check. Retries in Uptime Kuma, for: in Prometheus rules, a pending period in Grafana, FAIL_THRESHOLD in the script.
  2. Group. One message listing twelve hosts beats twelve messages; that is what group_by and group_wait are for.
  3. Repeat in hours, not minutes. repeat_interval: 4h for warnings, 1h for critical.
  4. Inhibit. The rule above silences warnings while a critical alert with the same labels is firing, so a dead host does not also report twenty dead services.
  5. Send resolved messages in the same style. A green card under every red one lets the channel read as pairs.
  6. Split severities across two webhooks. Critical goes to a channel nobody mutes; informational to one they can.
  7. Respect the rate limit. Fifty monitors flipping at once means fifty POSTs in one second, and Discord answers some with 429. Whether a tool retries depends on the tool; your own scripts must honour retry_after (webhook rate limits).

Common errors

{"message":"Unknown Webhook","code":10015} on every send. The URL is truncated (tokens are long and easy to cut when pasting) or the webhook was deleted. Recreate it and update the tool.

The test button works, real alerts never arrive. The notification is not attached to the monitor (Uptime Kuma), no policy routes to the contact point (Grafana), the receiver is not referenced in route (Alertmanager), or cron did not get the environment variable (the script). The test path skips all four.

400 Bad Request from a hand-written payload. An unescaped quote or newline in a variable, a hex colour string, or a field over its limit. Validate with jq . or build the payload with jq -n --arg; the full list is in webhook errors.

Alertmanager refuses to start with an unknown field discord_configs. The binary is older than 0.25. Upgrade, or fall back to webhook_configs and a proxy.

429 Too Many Requests during an incident. Too many monitors fired in one window. Group them, raise the retry threshold, honour retry_after.

The role mention shows as text, not a ping. Wrong role id, or a role from another server. Copy the id with Developer Mode (right-click the role → Copy Role ID).

FAQ

Does Uptime Kuma need a Discord bot to send alerts?

No. The Discord notification type takes only the webhook URL; a bot is needed only for things a webhook cannot do, such as interactive acknowledge buttons.

Which Alertmanager version supports discord_configs?

Alertmanager 0.25.0 and later. Older versions reject the field at config load; upgrade or route through a generic webhook receiver and a small proxy.

Can I send Grafana or Alertmanager alerts into a thread?

Yes. Both tools POST to exactly the URL you give them, so append ?thread_id=<existing thread id> to the webhook URL. Forum channels need thread_name in the body, which these tools do not set.

How do I stop getting a message every minute while a service is down?

Alert on state changes, not on every check. Uptime Kuma sends one DOWN and one UP by default; in Grafana and Alertmanager set repeat_interval to hours; in a cron script keep the last state in a file and post only when it differs.

Next steps

Start with the tool you already run, keep the palette to red, green and yellow, and configure grouping before the first real incident, not after. For a card that looks better than the defaults, build the JSON in the Discord Webhook Builder and paste it into your script.

Related reading:

Tags: uptime kumagrafanaalertmanagermonitoringalertsdiscord webhookdevops

Related articles

All articles

Build it in the visual editor

Embeds, Components V2, buttons and polls with a live Discord preview. Free, no signup.