Observer
Observer

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.

Was this page helpful?