Stato · webhook

Ricevi ogni incidente sul tuo endpoint.

POST JSON firmato ad ogni cambio di stato. Pronto per il tuo stack di osservabilità.

Iscriviti →

Eventi emessi

Tre tipi coprono il ciclo di vita dell'incidente.

  • incident.openedUn servizio passa a degradato, fuori servizio o manutenzione.
  • incident.updatedNota pubblica aggiunta dal team durante l'incidente.
  • incident.resolvedI servizi tornano operativi.

Header HTTP

Ogni richiesta porta le seguenti intestazioni.

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…f3a2

Struttura del payload

Oggetto JSON compatto.

{
  "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
}

Verifica della firma

HMAC-SHA256 su `timestamp.body`. Confronto a tempo costante. Drift > 5 min → rifiuta.

Node.js / TypeScript
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);
}
Python
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)

Canale Slack

Per una URL hooks.slack.com ricevi un messaggio Block Kit:

  • Header colorato per severità.
  • Severità + stato.
  • Riepilogo + ultimo aggiornamento.
  • Pulsante all'incidente.

Consegna e retry

Atteso 2xx entro 6s. Nessun retry automatico.

2xx · 6 s · no retry