Webhooks

The exact JSON Drumbeats sends your endpoint, and what happens when your endpoint is down.

This is what lands on your endpoint when a monitor goes down:

json
{
  "alertId": "0f2b8c31-6d4e-4a9f-8c11-2b7e5a1d9f30",
  "projectId": "8a1c4f22-9e3b-4d17-b5a8-6c2f0d4e7b19",
  "monitorId": "3d9e7b14-5c82-4f61-a0d3-7e1b9c5a2f48",
  "incidentId": "c71a3e58-2b64-4d90-9f7c-1a5e8d3b06f2",
  "event": "DOWN",
  "status": "down",
  "timestamp": "2026-08-01T02:07:14.882Z",
  "monitor": {
    "name": "Nightly backup",
    "slug": "nightly-backup",
    "description": "pg_dump to S3",
    "url": "https://drumbeats.io/monitors/3d9e7b14-5c82-4f61-a0d3-7e1b9c5a2f48"
  },
  "incident": {
    "event": "MISSED",
    "cause": "No check-in received within grace period",
    "startedAt": "2026-08-01T02:05:00.000Z"
  },
  "schedule": {
    "expected": "0 2 * * *",
    "gracePeriodSeconds": 300,
    "timezone": "Europe/Amsterdam",
    "lastPing": "2026-07-31T02:00:11.204Z"
  }
}
{
  "alertId": "0f2b8c31-6d4e-4a9f-8c11-2b7e5a1d9f30",
  "projectId": "8a1c4f22-9e3b-4d17-b5a8-6c2f0d4e7b19",
  "monitorId": "3d9e7b14-5c82-4f61-a0d3-7e1b9c5a2f48",
  "incidentId": "c71a3e58-2b64-4d90-9f7c-1a5e8d3b06f2",
  "event": "DOWN",
  "status": "down",
  "timestamp": "2026-08-01T02:07:14.882Z",
  "monitor": {
    "name": "Nightly backup",
    "slug": "nightly-backup",
    "description": "pg_dump to S3",
    "url": "https://drumbeats.io/monitors/3d9e7b14-5c82-4f61-a0d3-7e1b9c5a2f48"
  },
  "incident": {
    "event": "MISSED",
    "cause": "No check-in received within grace period",
    "startedAt": "2026-08-01T02:05:00.000Z"
  },
  "schedule": {
    "expected": "0 2 * * *",
    "gracePeriodSeconds": 300,
    "timezone": "Europe/Amsterdam",
    "lastPing": "2026-07-31T02:00:11.204Z"
  }
}

Use a webhook to bridge Drumbeats into Opsgenie, Datadog, an internal incident tool, or anything else that accepts inbound HTTP. For PagerDuty, use the dedicated PagerDuty channel instead. It maps the lifecycle for you.

Set one up#

  1. Open Settings → Notification Channels and click Add Channel → Webhook.
  2. Set the DOWN URL and the RECOVERY URL. They can be the same endpoint. Branch on the event field.
  3. Leave the mode on Simple unless the receiving system needs a specific body shape.
  4. Click Test. Drumbeats sends a sample DOWN and a sample RECOVERY to your endpoints.
  5. Add the channel to a notification group and assign the group to a monitor.

Read the payload#

Both events share a base. The type-specific block is what differs.

FieldTypeNotes
alertIdstringThe delivery's own ID. Use it to deduplicate retries
projectIdstringProject the monitor belongs to
monitorIdstringMonitor UUID
incidentIdstring or nullSet on DOWN. Null on RECOVERY
eventstringDOWN or RECOVERY
statusstringdown or up
timestampstringISO 8601, the time the payload was built
monitor.namestringDisplay name
monitor.slugstring or nullStable identifier, if you set one
monitor.descriptionstring or null
monitor.urlstringDeep link to the monitor in the dashboard

On a DOWN event you also get an incident object:

FieldTypeNotes
incident.eventstring or nullMISSED, FAILED, DURATION_HIGH, DURATION_LOW, or SSL_EXPIRING
incident.causestring or nullHuman-readable reason
incident.startedAtstring or nullISO 8601

On a RECOVERY event you get a recovery object instead:

json
{
  "event": "RECOVERY",
  "status": "up",
  "recovery": {
    "recoveredAt": "2026-08-01T03:14:02.115Z",
    "downSince": "2026-08-01T02:05:00.000Z",
    "resolvedIncidentCount": 1,
    "failuresBeforeRecovery": 0
  }
}
{
  "event": "RECOVERY",
  "status": "up",
  "recovery": {
    "recoveredAt": "2026-08-01T03:14:02.115Z",
    "downSince": "2026-08-01T02:05:00.000Z",
    "resolvedIncidentCount": 1,
    "failuresBeforeRecovery": 0
  }
}

A schedule object is attached to either event when the monitor has scheduling context, carrying expected, gracePeriodSeconds, timezone, and lastPing.

Certificate expiry adds an ssl block#

When incident.event is SSL_EXPIRING, the payload carries certificate detail. Match these key names exactly as written:

json
{
  "incident": { "event": "SSL_EXPIRING", "cause": "Certificate expires in 14 days", "startedAt": "2026-08-01T02:07:14.882Z" },
  "ssl": {
    "expires_at": "2026-08-15T09:31:00.000Z",
    "issuer": "Let's Encrypt",
    "days_remaining": 14,
    "threshold_days": 14
  }
}
{
  "incident": { "event": "SSL_EXPIRING", "cause": "Certificate expires in 14 days", "startedAt": "2026-08-01T02:07:14.882Z" },
  "ssl": {
    "expires_at": "2026-08-15T09:31:00.000Z",
    "issuer": "Let's Encrypt",
    "days_remaining": 14,
    "threshold_days": 14
  }
}

Region events add a region block#

Multi-location uptime monitors emit REGION_UNREACHABLE and REGION_RECOVERED events with a region object holding slug, display_name, failure_kind, and other_regions_up.

Build a custom body#

Switch the channel to Advanced mode when the receiving system needs a fixed shape. You then control the URL, the headers, and the body, and Drumbeats substitutes placeholders into all three.

Advanced body targeting a generic incident API
{
  "title": "$MONITOR_NAME is $STATUS",
  "severity": "critical",
  "source": "drumbeats",
  "dedup_key": "$MONITOR_ID",
  "details": $JSON
}
{
  "title": "$MONITOR_NAME is $STATUS",
  "severity": "critical",
  "source": "drumbeats",
  "dedup_key": "$MONITOR_ID",
  "details": $JSON
}
PlaceholderExpands to
$ALERT_IDDelivery ID
$PROJECT_IDProject UUID
$MONITOR_IDMonitor UUID
$INCIDENT_IDIncident UUID, empty on recovery
$EVENTDOWN or RECOVERY
$STATUSdown or up
$NOWCurrent time, ISO 8601
$MONITOR_NAMEMonitor display name
$MONITOR_SLUGMonitor slug
$MONITOR_DESCRIPTIONMonitor description
$INCIDENT_EVENTMISSED, FAILED, DURATION_HIGH, DURATION_LOW, SSL_EXPIRING
$INCIDENT_CAUSEHuman-readable reason
$STARTED_ATIncident start, ISO 8601
$RECOVERED_ATRecovery time, ISO 8601
$DOWN_SINCEOutage start, ISO 8601
$FRONTEND_URLDashboard base URL
$MONITOR_URLDeep link to the monitor
$JSONThe entire Simple-mode object, inlined as raw JSON

Drumbeats checks whether the rendered body parses as JSON. If it does not, the request is still sent as-is and a warning is logged, so a broken template shows up as a confusing 400 from your own endpoint rather than an error in Drumbeats.

Configure the request#

SettingDefaultRange
MethodPOSTGET, POST, or PUT
Timeout10 000 ms1000 to 60 000 ms
HeadersnoneOne Header-Name: value per line
BodySimple-mode payloadAdvanced mode only

GET requests send no body. Put what you need in the URL with placeholders.

Headers are where authentication goes. Authorization: Bearer <token> and X-API-Key: <key> both work, and placeholders are substituted in header values too.

What happens when your endpoint is down#

A delivery counts as failed when your endpoint returns a non-2xx status, the request times out, or the connection fails. What Drumbeats does next depends on the other channels in the group.

If every channel in the group failed, Drumbeats retries the delivery a few times over the following minute or so, with a widening gap between attempts. That is enough to ride out a brief blip at your end. After that it stops.

If any other channel in the group succeeded, the delivery is not retried. Your webhook failure is recorded and the alert moves on.

The incident itself is never affected by a delivery failure. It stays open in Drumbeats and resolves on the monitor's next success regardless of who got paged.

Per-delivery results are recorded, so you can see whether a webhook was attempted and what your endpoint returned.

Test before you rely on it#

Every webhook channel has a Test button that fires a sample DOWN and a sample RECOVERY at your configured URLs. The test payload uses incidentEvent: "TEST" so it is distinguishable from a real alert.

Send a test when you create the channel, and again every time you change the target system. A webhook that worked six months ago is not evidence it works now.

Next#

Notification channels for the other seven channel types. Groups for routing. Alert logic for what decides a delivery happens at all.