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"
}
}
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
Cron fires this once per maintenance row when scheduled_start_at
is within the next hour. Idempotent via
maintenance_starting_soon_fired_at.
{
"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:
X-Observer-Signature: sha256=<hex>
hex is HMAC-SHA-256(secret, raw_body). Recompute on the
receiving side and compare in constant time. Reject deliveries
whose signatures do not match.