Receive every incident on your endpoint.
A signed JSON POST on every incident open, update and resolve. Direct fit for your observability stack, no mail-client dependency.
Emitted events
Three types cover an incident's lifecycle. No heartbeat, no marketing event, only real status changes.
incident.openedA service moved to degraded, down, or maintenance.incident.updatedPublic note added by the team during the incident.incident.resolvedAffected services are back to operational.
HTTP headers
Every request arrives with these headers. The signature header is only present when a signing key is configured.
POST /your/endpoint HTTP/1.1
Host: ops.example.com
Content-Type: application/json
User-Agent: Coffrify-Status-Webhook/1.0
X-Coffrify-Event: incident.opened
X-Coffrify-Timestamp: 1717248000
X-Coffrify-Signature: t=1717248000,v1=5b8c…f3a2Payload shape
The body is a compact JSON object. The message field carries the latest team comment (can be null at open).
{
"type": "incident.opened",
"ts": "1717248000",
"incident": {
"id": "8c6a0c5e-…",
"slug": "eu-west-uploads-degraded",
"title": "Uploads dégradés en région eu-west",
"severity": "major",
"summary": "Latence accrue sur les uploads multipart depuis ~ 14:22 UTC.",
"affected_services": ["uploads", "api"],
"url": "https://status.coffrify.com/incident/eu-west-uploads-degraded"
},
"message": null
}Verifying the signature
HMAC-SHA256 over `timestamp.body` with the shared key, hex-encoded. Constant-time comparison against the v1 part of the header. Reject if the timestamp drifts by more than 5 minutes (anti-replay).
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody: string, header: string, secret: string): boolean {
const m = /t=(\d+),v1=([0-9a-f]+)/.exec(header);
if (!m) return false;
const [, ts, sig] = m;
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(sig, "hex");
return a.length === b.length && timingSafeEqual(a, b);
}import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
ts, sig = parts.get("t"), parts.get("v1")
if not ts or not sig: return False
if abs(time.time() - int(ts)) > 300: return False
expected = hmac.new(
secret.encode(),
f"{ts}.{raw_body.decode()}".encode(),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, sig)Slack channel
Subscribing a hooks.slack.com URL gives you a Block Kit message instead, built for in-channel scanning:
- Header tinted by severity.
- Severity + status in two monospace columns.
- Incident summary + latest update.
- “View incident” button.
Delivery & retry
Coffrify expects a 2xx response within 6 seconds. No automatic retry today: an unavailable endpoint misses the event. Watch your own uptime and backfill via /incidents.