How to Receive Webhooks from Uptime Kuma (Self-Hosted, Step by Step)

Receive Uptime Kuma monitor alerts on your own server: Additional Headers token authentication, the heartbeat/monitor/msg payload, Docker setup, and RabbitMQ routing, verified against real Kuma state changes.

By the end of this guide you will have a self-hosted endpoint that receives Uptime Kuma webhook notifications, authenticates every delivery with a secret header, and routes the alerts to a log, RabbitMQ, or any other destination. Everything below was verified end to end against a real Uptime Kuma instance and the real Core Webhook Module Docker image: a real monitor state change sent the deliveries quoted here, and the payload details come from Kuma's webhook provider source code.

How Uptime Kuma webhooks work

Uptime Kuma notifies you through pluggable notification providers; the Webhook provider POSTs JSON to your URL whenever an important heartbeat happens, which is a monitor state change: up goes down, down comes back up. The body has three fields:

{
  "heartbeat": {
    "monitorID": 1,
    "status": 0,
    "time": "2026-08-29 19:10:27.430",
    "msg": "Request failed with status code 404",
    "important": true,
    "duration": 60,
    "timezone": "UTC",
    "localDateTime": "2026-08-29 19:10:27"
  },
  "monitor": {
    "id": 1,
    "name": "gateway-health",
    "url": "https://example.com/health",
    "type": "http",
    "interval": 20,
    "active": true
  },
  "msg": "[gateway-health] [🔴 Down] Request failed with status code 404"
}

heartbeat.status is 0 for down and 1 for up, and msg is the human-readable summary. The request arrives with Content-Type: application/json and an axios user agent.

Authentication: Kuma does not sign webhook payloads. What it does have, straight from the provider source, is an Additional Headers field: a JSON object whose keys and values are merged into the request headers of every delivery. That is exactly where a secret token belongs, and the receiver compares it in constant time. The provider also supports multipart/form-data and a custom body template instead of the default JSON; this guide uses the default, and the payload is not signed in any mode.

One more field worth knowing: a monitor's Resend Notification if Down X times consecutively setting re-sends the down notification while an outage lasts; by default a state change notifies once.

Prerequisites

Step 1: Configure the receiver

Create a working directory with two files.

webhooks.json:

{
    "kuma_alerts": {
        "data_type": "json",
        "module": "log",
        "header_auth": {
            "header_name": "X-Webhook-Token",
            "api_key": "{$KUMA_WEBHOOK_TOKEN}",
            "case_sensitive": true
        }
    }
}

connections.json:

{}

What each parameter does:

  • kuma_alerts is the webhook ID; it becomes the URL path (/webhook/kuma_alerts).
  • module: log prints each authenticated alert to the container log. Zero dependencies for the first run; we swap in RabbitMQ at the end.
  • header_auth requires the X-Webhook-Token header on every request and compares it in constant time. case_sensitive: true makes the token value match exactly.
  • {$KUMA_WEBHOOK_TOKEN} pulls the token from an environment variable, so it never lives in a config file.

Generate a strong token (or use our webhook secret generator):

openssl rand -hex 32

Step 2: Run it

docker run -d --name webhook-gateway -p 8000:8000 \
  -v "$PWD/webhooks.json:/app/webhooks.json:ro" \
  -v "$PWD/connections.json:/app/connections.json:ro" \
  -e KUMA_WEBHOOK_TOKEN="your-generated-token" \
  spiderhash/webhook:latest

Confirm it is up:

docker logs webhook-gateway
# ... INFO: Application startup complete.

The receiver now answers on http://localhost:8000/webhook/kuma_alerts, with auto-generated API docs at http://localhost:8000/docs.

Step 3: Test it locally

Simulate exactly what Kuma sends. Save a realistic down event:

cat > down_event.json <<'EOF'
{"heartbeat":{"monitorID":1,"status":0,"time":"2026-08-29 19:10:27.430","msg":"Request failed with status code 404","important":true,"duration":60},"monitor":{"id":1,"name":"gateway-health","url":"https://example.com/health","type":"http","interval":20,"active":true},"msg":"[gateway-health] [🔴 Down] Request failed with status code 404"}
EOF

curl -s -w "\nHTTP %{http_code}\n" -X POST http://localhost:8000/webhook/kuma_alerts \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Token: your-generated-token" \
  --data-binary @down_event.json

Expected result:

{"message":"200 OK"}
HTTP 200

Now prove the authentication works. A wrong token:

{"detail":"Invalid API key in header: X-Webhook-Token"}
HTTP 401

And a request with no token header at all:

{"detail":"Missing required header: X-Webhook-Token"}
HTTP 401

Those are the exact responses the gateway returns. docker logs webhook-gateway shows the accepted payload.

Step 4: Create the notification in Uptime Kuma

In Kuma: profile menu > Settings > Notifications > Setup Notification, choose Webhook as the notification type:

  • Friendly Name: something like webhook-gateway
  • Post URL: https://your-domain/webhook/kuma_alerts
  • Request Body: application/json (the default)
  • Additional Headers: enable it and paste a JSON object:
{
  "X-Webhook-Token": "your-generated-token"
}

Use the Test button in the dialog to fire a test delivery, then save. Attach the notification to your monitors: either tick it in each monitor's Notifications list, or use the notification's "Default enabled" / "Apply on all existing monitors" checkboxes.

From then on, every state change delivers. In this guide's verification run, a monitor flipping to down delivered [🔴 Down] Request failed with status code 404 and the recovery delivered the matching up event, both accepted with 200.

Troubleshooting

  • 401 {"detail":"Invalid API key in header: X-Webhook-Token"}: the token in Additional Headers and KUMA_WEBHOOK_TOKEN differ. They must be byte-identical.
  • 401 {"detail":"Missing required header: X-Webhook-Token"}: Additional Headers is off, or the JSON there is not an object with the exact header name. Kuma rejects invalid JSON in that field with "Additional Headers is not a valid JSON" when sending.
  • Test works but real alerts never arrive: the notification is not attached to the monitor. The notification object and the per-monitor checkbox are separate; check the monitor's Notifications list.
  • You get one notification and nothing during a long outage: that is the default. Set the monitor's "Resend Notification if Down X times consecutively" if you want reminders while it stays down.

Going to production

Printing to logs is not a pipeline. Swap the module for a real destination; here is the RabbitMQ variant, verified end to end (a real Kuma state-change notification landed as a message in the kuma_alerts queue):

webhooks.json:

{
    "kuma_alerts": {
        "data_type": "json",
        "module": "rabbitmq",
        "connection": "rabbitmq_local",
        "module-config": {
            "queue_name": "kuma_alerts"
        },
        "header_auth": {
            "header_name": "X-Webhook-Token",
            "api_key": "{$KUMA_WEBHOOK_TOKEN}",
            "case_sensitive": true
        }
    }
}

connections.json:

{
    "rabbitmq_local": {
        "type": "rabbitmq",
        "host": "{$RABBITMQ_HOST:localhost}",
        "port": "{$RABBITMQ_PORT:5672}",
        "user": "{$RABBITMQ_USER:guest}",
        "pass": "{$RABBITMQ_PASS:guest}"
    }
}

The same pattern works for 17 other destinations: archive incident history to S3, publish to Kafka, or fan out to several at once with webhook chaining. Built-in rate limiting and retry handling cover the operational edges; when a shared dependency takes down fifty monitors at once, acknowledging fast and queueing durably is what keeps the alert trail complete. The full authentication reference, including header auth and 11 other methods, is in the docs.

Subscribe to Free Webhook Tool

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe