Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.tallwatch.com/llms.txt

Use this file to discover all available pages before exploring further.

This page documents the canonical webhook payload — the body you receive when you accept the default shape (no Handlebars template configured). If you’ve templated the body, this page is still useful as the source variable set you can interpolate from.

Headers

Every POST includes:
HeaderValueNotes
Content-Typeapplication/jsonAlways JSON, even when templated
User-AgentTallwatch/1.0Stable string for receiver-side allowlisting
X-Tallwatch-EventOne of incident.opened, incident.resolved, testMirrors event in the body
X-Tallwatch-DeliveryUUIDv4Unique per delivery — use for receiver-side deduplication
X-Tallwatch-Signaturesha256=<64-hex-chars>HMAC-SHA256 over the raw request body. Verify against your signing secret. See Signing

Top-level body

{
  "event": "incident.opened",
  "occurred_at": "2026-06-02T14:23:01.829Z",
  "org": { ... },
  "monitor": { ... },
  "incident": { ... }
}
event
string
required
One of incident.opened, incident.resolved, or test. Always present.
occurred_at
string (ISO 8601)
required
When the event happened, in UTC. ISO 8601 with millisecond precision.
org
object
required
The workspace that owns the monitor.
monitor
object | null
The monitor that fired the event. Always present for incident events. May be null only for event: "test" triggered without a specific monitor selected.
incident
object | null
The incident this event belongs to. Present for incident.opened and incident.resolved. null for event: "test".

org object

"org": {
  "id": "9b8c7d6e-...",
  "name": "Acme Corp",
  "slug": "acme"
}
org.id
string (UUID)
required
Stable identifier for the workspace.
org.name
string
required
The human-readable workspace name as shown in the dashboard.
org.slug
string
required
The URL-safe slug. Resolves to <slug>.tallwatch.com for the public status page.

monitor object

"monitor": {
  "id": "1a2b3c4d-...",
  "name": "API healthcheck",
  "type": "http",
  "url": "https://api.acme.com/health",
  "tags": ["prod", "api"]
}
monitor.id
string (UUID)
required
Stable identifier for the monitor.
monitor.name
string
required
The display name the user gave the monitor.
monitor.type
string
required
One of http, tcp, icmp, dns, ssl, keyword, heartbeat.
monitor.url
string | null
For http and keyword monitors: the URL being checked. For other types: null. To get protocol-specific details for other monitor types, query the Public API — they’re not duplicated into every webhook payload.
monitor.tags
string[]
Tags the user attached to the monitor. Empty array if none. Use these to route alerts on your side (e.g. prod tag → high-priority lane).

incident object

"incident": {
  "id": "abc12345",
  "status": "open",
  "opened_at": "2026-06-02T14:21:30.500Z",
  "acknowledged_at": null,
  "resolved_at": null,
  "duration_sec": null,
  "failing_regions": ["fra1", "iad1", "sgp1"]
}
incident.id
string
required
The Tallwatch incident event ID. This is the dispatch dedup key on your receiver side too — same ID never delivered twice.
incident.status
string
required
One of open, acknowledged, resolved. Matches the event field one-to-one for incident.opened (status: open) and incident.resolved (status: resolved).
incident.opened_at
string (ISO 8601)
required
When the incident first opened. Always present, even on incident.resolved events.
incident.acknowledged_at
string (ISO 8601) | null
When a user manually acknowledged. null if never acknowledged. Acknowledgement does NOT auto-fire a webhook event in v1 — this field exists for completeness on the resolved event.
incident.resolved_at
string (ISO 8601) | null
When the incident resolved. null on incident.opened, populated on incident.resolved.
incident.duration_sec
number | null
Time between opened_at and resolved_at in seconds. null on incident.opened. Server-computed from clock-skew-safe timestamps.
incident.failing_regions
string[]
The list of regions that voted the monitor down at the time the event fired. Region codes are fra1, iad1, hil1, sgp1, syd1. Empty array on incident.resolved.

Example: incident.opened

{
  "event": "incident.opened",
  "occurred_at": "2026-06-02T14:23:01.829Z",
  "org": {
    "id": "9b8c7d6e-3a4b-5c6d-7e8f-9a0b1c2d3e4f",
    "name": "Acme Corp",
    "slug": "acme"
  },
  "monitor": {
    "id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
    "name": "API healthcheck",
    "type": "http",
    "url": "https://api.acme.com/health",
    "tags": ["prod", "api"]
  },
  "incident": {
    "id": "abc12345",
    "status": "open",
    "opened_at": "2026-06-02T14:21:30.500Z",
    "acknowledged_at": null,
    "resolved_at": null,
    "duration_sec": null,
    "failing_regions": ["fra1", "iad1", "sgp1"]
  }
}

Example: incident.resolved

{
  "event": "incident.resolved",
  "occurred_at": "2026-06-02T14:38:47.012Z",
  "org": {
    "id": "9b8c7d6e-3a4b-5c6d-7e8f-9a0b1c2d3e4f",
    "name": "Acme Corp",
    "slug": "acme"
  },
  "monitor": {
    "id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
    "name": "API healthcheck",
    "type": "http",
    "url": "https://api.acme.com/health",
    "tags": ["prod", "api"]
  },
  "incident": {
    "id": "abc12345",
    "status": "resolved",
    "opened_at": "2026-06-02T14:21:30.500Z",
    "acknowledged_at": null,
    "resolved_at": "2026-06-02T14:38:47.012Z",
    "duration_sec": 1037,
    "failing_regions": []
  }
}

Example: test

The test alert fires when you click Send test alert on the channel form.
{
  "event": "test",
  "occurred_at": "2026-06-02T14:00:00.000Z",
  "org": {
    "id": "9b8c7d6e-3a4b-5c6d-7e8f-9a0b1c2d3e4f",
    "name": "Acme Corp",
    "slug": "acme"
  },
  "monitor": null,
  "incident": null
}
A test payload has monitor and incident as null so your receiver can branch on event === "test" to short-circuit before processing.

Dedup on your side

The incident.id is the natural dedup key. Tallwatch’s own alert_dispatches table guarantees we send each (incident_event_id, channel_id) exactly once — but network retries between us and you can still cause a duplicate POST if your receiver times out before responding. Two options for your receiver:
  • Cheap: store the last N delivery IDs (X-Tallwatch-Delivery header) in a TTL’d set. Reject duplicates.
  • Proper: idempotently upsert on incident.id + event so any number of retries converge to one row.
The proper option is more work but is the correct shape for a production receiver.

What changes when you use a Handlebars template

The payload becomes whatever your template renders. The variables available inside the template mirror the canonical shape — {{monitor.name}}, {{incident.failing_regions}}, etc. See Templates for the full helper set and example templates.