Webhook signé
Le webhook signé relie Perstat à vos propres destinations : un outil d’incidents qui accepte des sources d’alerte génériques, une automatisation que vous exploitez vous-même, ou un script. Les destinations de chat, PagerDuty et Opsgenie ont leurs propres connecteurs dédiés.
Créer un point d’arrivée
Section intitulée « Créer un point d’arrivée »Dans l’application, sous Intégrations, créez un connecteur de type Webhook et donnez-lui une URL (propriétaires et administrateurs). Perstat génère un secret de signature et l’affiche exactement une fois. Ensuite il apparaît masqué, et vous pouvez le renouveler à tout moment. Le renouvellement remplace le secret immédiatement et l’ancien cesse aussitôt de vérifier : mettez donc à jour votre récepteur dans la même foulée.
Avec webhooks:write, l’API REST crée, modifie, teste et supprime les webhooks et
renouvelle leurs secrets, si bien que vos scripts de provisionnement peuvent aussi les gérer.
Le secret a la forme whsec_ suivi de 64 caractères hexadécimaux en minuscules. Traitez la chaîne
entière comme une clé opaque.
Les URL en http:// sont acceptées pour des destinations locales. Utilisez https:// pour tout ce
qui traverse un réseau qui n’est pas le vôtre. La cible doit être joignable publiquement : un hôte
qui se résout en adresse privée, de bouclage ou de lien local est refusé à chaque envoi.
Quand une livraison part
Section intitulée « Quand une livraison part »Ce que reçoit un point d’arrivée dépend de l’existence d’une chaîne d’escalade active dans votre organisation : une formule avec le planning d’astreinte (à partir de Sentinel, voir formules et limites), une astreinte activée et une chaîne active.
Sans chaîne active (dans toute autre formule, ou si l’astreinte ou la chaîne sont désactivées), chaque point d’arrivée actif reçoit les événements auxquels il est abonné sous Intégrations :
| Événement | Quand |
|---|---|
incident.opened |
Un incident s’ouvre, critique ou dégradé. Une alerte retenue par une règle de dépendance est livrée dès qu’elle est libérée. |
incident.resolved |
L’incident se ferme. |
incident.test |
Vous appuyez sur Test sur le connecteur. Livré de façon synchrone, à ce seul point d’arrivée. |
Avec une chaîne active, c’est la chaîne qui pilote les messages d’incident sortants. Un point d’arrivée reçoit les messages des incidents critiques, depuis les étapes qui le nomment comme cible, à la minute 0 ou plus tard :
| Événement | Quand |
|---|---|
incident.escalation |
Une étape qui nomme le point d’arrivée se déclenche. C’est aussi le premier message pour une étape à la minute 0 : l’événement dit « cet incident n’est pas acquitté », pas « un nouvel incident vient de s’ouvrir ». |
incident.resolved |
L’incident se ferme. Il part vers chaque point d’arrivée qu’une étape a alerté, abonné ou non, et vers chaque point d’arrivée actif qui y est abonné. Ce que la chaîne ouvre, la chaîne le ferme. |
incident.test |
Comme ci-dessus. |
Avec une chaîne active, l’abonnement à incident.opened reste sans effet. Une étape ignore un
point d’arrivée désactivé, et elle ignore tous les points d’arrivée tant que l’incident est
acquitté, tant que son monitor est désactivé ou archivé, et tant que le monitor se trouve dans une
fenêtre de maintenance. Un point d’arrivée désactivé en cours d’incident reçoit malgré tout la fin
d’alerte d’un incident pour lequel il a été alerté.
La requête
Section intitulée « La requête »Un POST par événement, avec un corps JSON :
POST <votre URL>content-type: application/jsonuser-agent: perstat-webhook/1.0x-perstat-signature: sha256=<64 caractères hexadécimaux en minuscules>{ "event": "incident.escalation", "incident": { "id": "inc_9f3c2a7e1b4d48c0a6e5f1d2c3b4a596", "severity": "critical", "cause": "HTTP 503", "summary": "L'API ne répond pas", "monitor": { "id": "mon_4b1e8d2c7a9f4e6b8c0d1e2f3a4b5c6d", "name": "API" }, "started_at_unix": 1757930400, "resolved_at_unix": null }, "organization": { "id": "org_2d7a9c4e1f3b4a5c8d6e7f8a9b0c1d2e", "name": "Acme" }}| Champ | Signification |
|---|---|
event |
L’un des événements ci-dessus. |
incident.id |
Stable sur tous les événements d’un même incident. Servez-vous-en pour ouvrir et fermer le même cas de votre côté. PagerDuty le reçoit comme dedup_key, Opsgenie comme alias. |
incident.severity |
critical ou degraded. Par une chaîne active, vous recevez critical. Le test envoie aussi critical. |
incident.cause |
Texte libre : le détail du premier check échoué, ou la cause saisie sur un incident manuel. test pour une livraison de test. |
incident.summary |
Facultatif, null si vide. |
incident.monitor |
id et name du monitor lié. null pour un incident manuel sans monitor et pour le test. |
incident.started_at_unix |
Secondes Unix. |
incident.resolved_at_unix |
Secondes Unix, null tant que l’incident n’est pas résolu. |
organization |
id et name de l’organisation à laquelle appartient le point d’arrivée. |
Le corps est stable et s’enrichit par ajout : lisez ce dont vous avez besoin et ignorez les champs que vous ne connaissez pas.
Vérifier la signature
Section intitulée « Vérifier la signature »L’en-tête porte sha256= suivi du HMAC-SHA256 du corps brut de la requête, avec pour clé la
chaîne du secret exactement telle qu’elle vous a été montrée : ses octets ASCII sont la clé, elle
n’est pas décodée. Calculez le MAC sur les octets reçus, avant toute analyse JSON, et comparez en
temps constant.
Node :
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, rawBody, header) { const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex"); return expected.length === header.length && timingSafeEqual(Buffer.from(expected), Buffer.from(header));}Python :
import hashlibimport hmac
def verify(secret: str, raw_body: bytes, header: str) -> bool: expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, header)Un corps qui échoue à la vérification ne vient pas de Perstat, ou a été modifié en chemin.
Répondez 401 et ne faites rien d’autre.
La signature couvre le seul corps, sans horodatage ni identifiant de livraison, si bien qu’une
requête capturée passe de nouveau la vérification quand elle est rejouée. Livrez sur TLS et traitez
un couple (incident.id, event) répété comme déjà traité : pour un outil d’incidents, fermer deux
fois une alerte est sans conséquence, l’ouvrir deux fois est du bruit.
Règles de livraison
Section intitulée « Règles de livraison »- Une réponse
2xxvaut livraison. Tout autre statut, y compris un3xx, vaut échec, et les redirections ne sont pas suivies. - Le délai est de 10 secondes par livraison. Répondez vite et faites le travail ensuite.
- Perstat livre chaque événement une fois.
- Le résultat de la dernière livraison, avec le texte d’erreur s’il y en a eu un, est affiché sur le connecteur sous Intégrations et à côté de l’étape dans la chaîne.
Gardez le récepteur disponible, et rapprochez les événements manqués via l’API REST :
lisez un incident par son incident.id, ou listez avec incidents:read les incidents ouverts de
l’organisation.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Alertes et astreinte : placer le point d’arrivée dans une chaîne d’escalade.
- API : gérer les webhooks et lire les incidents avec une clé API.