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

HTTP
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

JSON
{
  "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_statedown, degraded, or operational (on close events).
  • closed_at — populated on incident.closed only.
  • monitors_affected — the current set of state='down' Monitors in the Service at dispatch time. May be empty on incident.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

JavaScript
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

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_id twice.
  • Order is best-effort. Two transitions in quick succession may arrive out of order. Trust opened_at and closed_at over 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.
Webhooks · Docs · Glimly · Glimly