04Webhooks
One signed POST,
per Incident transition.
Glimly POSTs a signed JSON payload to your configured URL every time an Incident opens or closes. One delivery per transition. Verify the signature before trusting the body.
Setup
In the dashboard, go to Settings → Notifications, paste your endpoint URL, and Save. A signing secret is generated automatically on first save. Use the Send test button to round-trip a fake delivery against your endpoint before a real Incident fires.
Events
incident.opened— a Service transitions from operational to degraded or down.incident.closed— a previously open Incident's Service returns to operational.test— sent on demand from the Notifications page. Different payload shape.
Severity changes inside an open Incident (degraded → down or vice-versa) do not fire a new event; they update the existing Incident in place. This avoids alert-storming on flapping Services.
Request shape
POST <your URL>
Content-Type: application/json
User-Agent: Glimly-Webhook/1.0
X-Glimly-Event: incident.opened
X-Glimly-Timestamp: 1747832400
X-Glimly-Signature: sha256=<hex>Payload
{
"event": "incident.opened",
"event_id": "abc123:opened",
"account_id": "acct_xxx",
"service_id": "svc_xxx",
"service_name": "API",
"service_state": "down",
"incident_id": "abc123",
"opened_at": "2026-05-21T14:32:01.000Z",
"closed_at": null,
"monitors_affected": [
{ "id": "mon_xxx", "name": "Health endpoint", "url": "https://api.acme.com/health" }
]
}event_id— stable per transition (<incident_id>:<opened|closed>). Use for dedupe.service_state—down,degraded, oroperational(on close events).closed_at— populated onincident.closedonly.monitors_affected— the current set ofstate='down'Monitors in the Service at dispatch time. May be empty onincident.closed.
The test payload has a different shape: { event, event_id, account_id, sent_at, message }.
Signature verification
Glimly signs every request with HMAC-SHA256 over <timestamp>.<raw body> using your Account's signing secret. Note the dot separator — and the requirement to use the raw body bytes, not the JSON-parsed object.
Node.js
import crypto from 'node:crypto';
export function verify(req, secret) {
const ts = req.headers['x-glimly-timestamp'];
const sig = req.headers['x-glimly-signature']; // "sha256=<hex>"
if (!ts || !sig) return false;
const expected =
'sha256=' +
crypto.createHmac('sha256', secret).update(`${ts}.${req.rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}Python
import hmac, hashlib
def verify(headers, raw_body, secret):
ts = headers.get('X-Glimly-Timestamp')
sig = headers.get('X-Glimly-Signature', '').removeprefix('sha256=')
if not ts or not sig:
return False
expected = hmac.new(
secret.encode(),
f"{ts}.{raw_body}".encode(),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(sig, expected)Delivery semantics
- Best-effort, single attempt. One POST per transition, 5-second timeout. Non-2xx responses are logged but not retried.
- Idempotent on event_id. SQS may redeliver the same evaluation message if the evaluator Lambda errors after the DB transition but before alert dispatch — you may receive the same
event_idtwice. - Order is best-effort. Two transitions in quick succession may arrive out of order. Trust
opened_atandclosed_atover arrival order. - No IP allowlist in v1. Dispatch egress is the Lambda's NAT; the IP changes. Use the signature header for authenticity.
Best practices
- Return 2xx within 5 seconds; defer heavy work to a background queue inside your system.
- Persist the
event_ids you've processed to short-circuit duplicates. - Plumb webhook failures into your own monitoring — Glimly does not surface delivery failures in the dashboard in v1.