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