Webhook payload reference
JSON shapes for every event type Observer emits.
Every webhook delivery is a POST with a JSON body and the headers:
Content-Type: application/json
X-Observer-Event: <event-type>
X-Observer-Delivery: <delivery-uuid>
X-Observer-Signature: sha256=<hex> (when a signing secret is configured)
The body is always:
{
"event_type": "<event-type>",
"event_id": "<delivery-uuid>",
"occurred_at": "<ISO8601>",
"data": { ... }
}
The data field shape varies by event type. The reference below
documents each.
metric.status_changed
A metric's status flipped after dwell gating.
{
"data": {
"org_id": "org_...",
"metric_id": "<uuid>",
"metric_title": "checkout-api 5xx ratio",
"old_status": "healthy",
"new_status": "unhealthy",
"value": 0.024,
"timestamp": "<ISO8601>"
}
}
metric.no_data
A metric entered no_data: the agent could not collect a sample.
{
"data": {
"org_id": "org_...",
"metric_id": "<uuid>",
"metric_title": "checkout-api 5xx ratio",
"reason": "ECONNREFUSED",
"timestamp": "<ISO8601>"
}
}
page.status_changed
A status page's rolled-up status flipped.
{
"data": {
"org_id": "org_...",
"page_id": "<uuid>",
"page_title": "Acme Cloud",
"old_status": "healthy",
"new_status": "degraded",
"computed_at": "<ISO8601>"
}
}
slo.burn_started
An SLO crossed below its target.
{
"data": {
"org_id": "org_...",
"slo_id": "<uuid>",
"slo_name": "checkout-api availability",
"service_id": "<uuid>",
"service_name": "checkout-api",
"burn_event_id": "<uuid>",
"started_at": "<ISO8601>",
"error_budget_burned_pct": 12.4,
"target_pct": 99.9,
"window_days": 30
}
}
slo.burn_resolved
An SLO recovered. The matching burn_event_id from the prior
slo.burn_started is included so consumers can pair the two.
{
"data": {
"org_id": "org_...",
"slo_id": "<uuid>",
"slo_name": "checkout-api availability",
"service_id": "<uuid>",
"service_name": "checkout-api",
"burn_event_id": "<uuid>",
"resolved_at": "<ISO8601>",
"final_budget_remaining_pct": 87.2,
"target_pct": 99.9,
"window_days": 30
}
}
agent.offline
An agent missed its expected heartbeat window.
{
"data": {
"org_id": "org_...",
"agent_id": "<uuid>",
"agent_name": "agent-eu-west-1",
"last_heartbeat_at": "<ISO8601>",
"version": "1.2.3"
}
}
agent.duplicate_key
Two agent processes are sharing one key. Only the most recently
started process is ingested; the older one is fenced and its samples
are dropped. Fires with state open when the stale process is first
detected, and cleared once it has been quiet for ten minutes.
{
"data": {
"org_id": "org_...",
"agent_id": "<uuid>",
"agent_name": "agent-eu-west-1",
"state": "open",
"at": "<ISO8601>",
"stale_started_at": "<ISO8601>",
"active_started_at": "<ISO8601>"
}
}
incident.created
A new incident row was created. Fires for both drafts and published incidents on insert.
{
"data": {
"org_id": "org_...",
"incident_id": "<uuid>",
"title": "...",
"severity": "major",
"state": "draft",
"is_customer_scoped": false,
"affected_service_ids": ["<uuid>"],
"affected_service_names": ["checkout-api"]
}
}
incident.published
A draft incident was published. The incident is now visible on the public page.
{
"data": {
"org_id": "org_...",
"incident_id": "<uuid>",
"title": "...",
"severity": "major",
"state": "published",
"published_at": "<ISO8601>",
"is_customer_scoped": false,
"affected_service_ids": ["<uuid>"],
"affected_service_names": ["checkout-api"]
}
}
incident.updated
Title, severity, affected services, or visibility changed.
{
"data": {
"org_id": "org_...",
"incident_id": "<uuid>",
"changed_fields": ["title", "severity"]
}
}
incident.message_added
A new message was appended to an incident timeline.
{
"data": {
"org_id": "org_...",
"incident_id": "<uuid>",
"message_id": "<uuid>",
"message_type": "Identified",
"description": "...",
"occurred_at": "<ISO8601>"
}
}
incident.resolved
resolved_at was set on the incident. Posting a Resolved message
also fires this event because the appendMessage path auto-flips
the parent state.
{
"data": {
"org_id": "org_...",
"incident_id": "<uuid>",
"title": "...",
"severity": "major",
"resolved_at": "<ISO8601>"
}
}
incident.deleted
An incident was soft-deleted via DELETE.
{
"data": {
"org_id": "org_...",
"incident_id": "<uuid>",
"deleted_at": "<ISO8601>"
}
}
incident.auto_drafted
The auto-incident worker created a DRAFT incident from an unhealthy metric flip. The draft is not visible to customers until it is published via the email CTA, the console, or the API.
{
"data": {
"org_id": "org_...",
"incident_id": "<uuid>",
"metric_id": "<uuid>",
"metric_title": "checkout-api 5xx ratio",
"severity": "major",
"trigger_reason": "checkout-api 5xx ratio read 0.04 against threshold 0.02 at <ISO8601>",
"value": 0.04,
"threshold": 0.02,
"affected_service_ids": ["<uuid>"],
"url": "https://use.observer/console/<org>/updates/edit/<incident_id>"
}
}
incident.auto_published
An auto-drafted incident was published via the signed-token email
link. Equivalent to incident.published but distinguished so
subscribers can listen specifically for the auto-publish flow.
{
"data": {
"org_id": "org_...",
"incident_id": "<uuid>",
"title": "Investigating elevated errors on checkout-api 5xx ratio",
"severity": "major",
"state": "published",
"published_at": "<ISO8601>",
"affected_service_ids": ["<uuid>"],
"url": "https://use.observer/console/<org>/updates/edit/<incident_id>"
}
}
incident.auto_dismissed
An auto-drafted incident was dismissed via the signed-token email
link, OR auto-expired after 24h with no action. reason is
operator_dismiss or auto_expired.
{
"data": {
"org_id": "org_...",
"incident_id": "<uuid>",
"title": "Investigating elevated errors on checkout-api 5xx ratio",
"dismissed_at": "<ISO8601>",
"reason": "auto_expired"
}
}
maintenance.scheduled
A maintenance window was created.
{
"data": {
"org_id": "org_...",
"maintenance_id": "<uuid>",
"title": "...",
"scheduled_start_at": "<ISO8601>",
"scheduled_end_at": "<ISO8601>",
"affected_service_ids": ["<uuid>"],
"affected_service_names": ["checkout-api"]
}
}
maintenance.starting_soon
Fires when scheduled_start_at is within the next hour. Fires at
most once per maintenance.
{
"data": {
"org_id": "org_...",
"maintenance_id": "<uuid>",
"title": "...",
"scheduled_start_at": "<ISO8601>"
}
}
maintenance.started
actual_start_at was set (manual API call or cron auto-transition).
{
"data": {
"org_id": "org_...",
"maintenance_id": "<uuid>",
"title": "...",
"actual_start_at": "<ISO8601>",
"scheduled_end_at": "<ISO8601>"
}
}
maintenance.completed
actual_end_at was set. Posting a Resolved message on a
maintenance also fires this event because the appendMessage path
flips the parent state.
{
"data": {
"org_id": "org_...",
"maintenance_id": "<uuid>",
"title": "...",
"actual_start_at": "<ISO8601>",
"actual_end_at": "<ISO8601>"
}
}
maintenance.canceled
canceled_at was set before completion.
{
"data": {
"org_id": "org_...",
"maintenance_id": "<uuid>",
"title": "...",
"canceled_at": "<ISO8601>"
}
}
Signature verification
When a signing secret is configured, every delivery carries three headers:
X-Observer-Signature: sha256=<hex>
X-Observer-Signature-Legacy: sha256=<hex>
X-Observer-Timestamp: <unix-millis>
The primary X-Observer-Signature signs the timestamp and the raw
body together: hex is HMAC-SHA-256(secret, "${timestamp}.${rawBody}"),
where timestamp is the value in X-Observer-Timestamp. Recompute
this on the receiving side and compare in constant time. Reject
deliveries whose signatures do not match, and reject deliveries whose
timestamp is more than five minutes from the receiver's clock to bound
replay.
X-Observer-Signature-Legacy signs the body only:
HMAC-SHA-256(secret, rawBody). It is deprecated and exists for
receivers that have not migrated to the timestamp-prefixed scheme.
Verify against X-Observer-Signature for new integrations.