Webhooks
An HTTPS endpoint receives a JSON body for every event a rule routes to it immediately. Each delivery is signed, carries ids you can use to deduplicate, and is retried with backoff.
Add a webhook destination
Webhooks are created through the API with a key that holds destinations:write, so the signing secret can be returned once. The URL must be HTTPS.
curl -X POST https://api.changelog.observer/v1/projects/<project_id>/destinations \
-H "Authorization: Bearer <key>" -H "Content-Type: application/json" \
-d '{"type": "webhook", "name": "On-call runner", "config": {"url": "https://example.com/hooks/changelog"}}'The response carries signing_secret (whsec_…) once. Store it where your receiver can read it. Then point a rule at the destination, or rely on the defaults (urgent and high-priority events go out at once).
The request
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | changelog.observer-webhooks/1 |
X-CO-Event-Id | The event’s id. Several deliveries of one event (retries, several webhooks) share it. |
X-CO-Delivery-Id | This delivery attempt’s id. Use it to make your receiver idempotent. |
X-CO-Timestamp | Unix seconds when the request was signed. |
X-CO-Signature | v1=<hex HMAC-SHA256> over {timestamp}.{body} with your signing secret. |
The body is the event as the API returns it, plus the rule that routed it and the delivery:
{
"id": "evt_…", "project_id": "prj_…",
"dependency": { "id": "dep_…", "name": "stripe", "environment": ["production"] },
"category": "incident",
"title": "Elevated error rates on the Payments API",
"summary": "…",
"source": { "type": "status_page", "url": "https://…", "published_at": "…", "first_seen_at": "…" },
"severity": { "upstream": "critical", "platform_priority": "urgent", "customer_impact": "unknown", "confidence": 0.95 },
"delivery": { "webhook_eligible": true, "digest_eligible": false },
"provenance": { "ingestion_method": "status_api", "parser_version": "statuspage-v1", "duplicate_of": null, "warnings": [] },
"rule": "default rules",
"delivery": { "id": "dlv_…", "attempt": 1, "kind": "event" }
}customer_impact is unknown unless a person set it. Do not let a receiver treat an urgent upstream incident as a confirmed outage of your own.
Verify the signature
Compute HMAC-SHA256 over the timestamp, a dot, and the raw body, with the signing secret; compare in constant time; reject timestamps older than five minutes to stop replays.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret, headers, rawBody) {
const ts = headers["x-co-timestamp"];
const sig = headers["x-co-signature"] ?? "";
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = "v1=" + createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
return expected.length === sig.length && timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}Use the raw request body exactly as received; re-serialising JSON changes bytes and breaks the signature. The same function exists in the product’s own code as verifyWebhookSignature.
Responses and retries
Answer with any 2xx within the timeout and the delivery is recorded as delivered. A 5xx, a timeout or a network error is retried with exponential backoff, up to eight attempts; a 4xx (other than 408 and 429) is not retried. After the last attempt the delivery is exhausted and shows on the project’s Deliveries page with the last response, where it can be retried by hand or with POST …/deliveries/{attemptId}/retry.
What gets delivered
- Only events a rule (or the defaults) routes as immediate. Digest-mode events go by email in the digest, not to webhooks.
- Never duplicates: later updates to an incident attach to its timeline and do not fire again.
- Never during quiet hours unless the project allows critical incidents through; never for a paused project or a muted dependency.
- At a usage cap, only urgent upstream incidents.
A common pattern is a receiver that forwards to your CI or your own agent runner, which then asks the MCP server for the incident’s detail and decides what to do.