Webhooks
Subscribe your own app to Ionhour events — configuring delivery, the signed envelope format, verifying HMAC signatures, rotating secrets, and retry behavior.
Subscribed events are POSTed to your app's delivery endpoint — one delivery per installation, signed with your webhook signing secret.
Configuring delivery
On the app's Webhooks tab set an https endpoint URL and tick the events you want:
| Event | Fires when | Gated by |
|---|---|---|
check.up | A check recovers | checks:read |
check.down | A check goes down | checks:read |
check.late | A check misses its schedule | checks:read |
check.paused | A check is paused — manually or by a deployment window (data.trigger says which) | checks:read |
check.resumed | A paused check resumes | checks:read |
job.up | A heartbeat job recovers | jobs:read |
job.down | A heartbeat job goes down | jobs:read |
job.late | A heartbeat job misses its schedule | jobs:read |
incident.created | An incident opens | incidents:read |
incident.acknowledged | An incident is acknowledged | incidents:read |
incident.resolved | An incident resolves | incidents:read |
Check events carry data.checkId; job events carry data.jobId — checks (outbound probes) and jobs (inbound heartbeats) are separate domains with separate scopes. More event families (deployments, maintenance windows, dependencies, status pages) are planned and will be added to this list additively.
Scope gating. An event is delivered to an installation only if that installation granted the event's backing read scope. Subscribing without the scope isn't an error — the event is simply never delivered (the console warns you). Note that gating is per installation, not per app: if your app requests checks:read but a particular workspace's installation granted fewer scopes, that installation receives nothing for the gated events. A webhook payload carries the same information a read would, so this keeps event delivery inside the consent your users granted.
The delivery envelope
{
"id": "evt_…",
"type": "incident.created",
"createdAt": "2026-07-19T20:15:00.000Z",
"workspaceId": "…",
"installationId": "…",
"data": { }
}Verifying signatures
Every delivery carries two headers:
X-Ionhour-Timestamp— Unix timestamp of the sendX-Ionhour-Signature—v1=<hex HMAC-SHA256>computed over<timestamp>.<raw body>with yourionh_whsec_…secret
Verify before trusting a payload:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(secret, timestamp, rawBody, signatureHeader) {
const skewSeconds = Math.abs(Date.now() / 1000 - Number(timestamp));
if (skewSeconds > 300) return false; // reject > 5 min skew (replay guard)
const expected = `v1=${createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex')}`;
return timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}Compute the HMAC over the raw request body, before any JSON parsing — re-serialized JSON will not match.
Secret rotation
Rotate the signing secret from the Webhooks tab. The new secret takes over immediately, and the previous secret stays valid for 24 hours — verify against both during that window and you can roll your fleet without dropping deliveries.
Retries and delivery health
Failed deliveries are retried with exponential backoff and logged. Sustained failure surfaces as delivery-health on the workspace's Installed Apps page and can eventually auto-pause the subscription. Deliveries stop immediately when an app is suspended or rejected, or when an installation is revoked.
Scopes & product APIs
Every OAuth scope on the Ionhour developer platform, grouped by product API — monitoring, incidents, alerting, on-call, and status pages — plus capping rules.
API Reference
Complete reference for the Ionhour REST API — authentication, checks, signals, incidents, on-call schedules, status pages, deployments, and event webhooks.