Webhook signé
Le webhook signé est le connecteur pour 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 ; cette page ne traite que du type générique. Tout ce qui suit décrit ce qui est livré aujourd’hui.
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étaire et administrateur seulement). Perstat génère un secret de signature et l’affiche exactement une fois ; ensuite il n’apparaît que masqué et ne peut plus qu’être renouvelé. Le renouvellement remplace le secret immédiatement. Il n’y a pas de période de recouvrement pendant laquelle les deux seraient valables.
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 : un forfait avec le planning d’astreinte (à partir de Sentinel, voir forfaits et limites), une astreinte activée et une chaîne active.
Sans chaîne active (dans tout autre forfait, 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 n’apprend une panne que si une étape le nomme comme cible, à la minute 0 ou plus tard, et seulement pour les incidents critiques :
| É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 moniteur est désactivé ou archivé, et tant que le
moniteur 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 ne voyez que critical ; le test envoie aussi critical. |
incident.cause |
Texte libre : le détail de la première vérification échouée, 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 moniteur lié. null pour un incident manuel sans moniteur 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 n’est étendu que 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 ne porte ni horodatage ni identifiant de livraison. Une requête capturée
peut donc être rejouée et passera de nouveau la vérification. 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 le reste, y compris un3xx, vaut échec ; les redirections ne sont pas suivies. - Le délai est de 10 secondes par livraison. Répondez vite et faites le travail ensuite.
- Une tentative par événement. Il n’y a pas de file de reprise ; une livraison échouée reste échouée.
- 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.
Comme il n’y a pas de reprise, un récepteur brièvement indisponible manque cet événement.
S’il vous faut le tableau complet plutôt que le dernier message, lisez l’incident via
l’API à partir de incident.id.