Ce que vous obtiendrez
- Un
POSTvers votre adresse pour chaque alerte : problèmes, avertissements, rétablissements et prises en charge, en JSON, selon les mêmes règles que pour tous les autres canaux. - Une signature sur chaque requête, faite avec un secret que seuls vous et Gryphon détenez, pour que votre récepteur puisse refuser tout ce que Gryphon n’a pas envoyé, et tout ce qui est rejoué plus tard.
- Un point de départ pour n’importe quoi : ouvrir un billet, alerter quelqu’un par votre propre outil, allumer une lampe, publier dans une messagerie pour laquelle Gryphon n’a pas d’intégration.
Avant de commencer
- Une adresse HTTPS sur Internet qui accepte un
POSTet répond en moins de dix secondes. Le HTTP simple est refusé : le corps nomme vos hôtes. - Dans Gryphon, le propriétaire du compte, ou un membre que le propriétaire a autorisé à modifier les hôtes.
Étapes
1Ajouter le webhook
Dans le tableau de bord, ouvrez Intégrations et choisissez Webhook. Dans l’application iPhone, c’est dans le menu du compte ; sur Android, sous Paramètres.
- Nom
- Robot d’incidents
- URL du webhook
- https://example.com/gryphon
- Ce qu’elle envoie
- Problèmes, Avertissements, Rétablissements, Prises en charge
- Quels hôtes
- Tous les hôtes, ou seulement ceux que vous choisissez
2Copier le secret de signature
L’enregistrement vous amène à la page du webhook, où s’affiche son secret de signature, une chaîne qui commence par whsec_. Copiez-le dans la configuration de votre récepteur, par exemple sous GRYPHON_WEBHOOK_SECRET. Il peut être affiché de nouveau sur cette page, et Créer un nouveau secret le remplace : l’ancien cesse aussitôt de fonctionner.
3Vérifier la signature dans votre récepteur
Chaque requête porte un en-tête Gryphon-Signature comme t=1791310863,v1=5b2c…. t est le moment de la signature, en secondes Unix ; v1 est le HMAC-SHA256 en hexadécimal de t, d’un point et du corps brut, avec votre secret. Recalculez-le sur le corps exactement tel qu’il est arrivé (avant toute analyse JSON), comparez en temps constant, et refusez un horodatage qui s’écarte de plus de quelques minutes. Chacun de ces exemples a été vérifié avec une requête signée par Gryphon.
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"math"
"net/http"
"os"
"strconv"
"strings"
"time"
)
var secret = []byte(os.Getenv("GRYPHON_WEBHOOK_SECRET"))
// verify reports whether a request came from Gryphon, recently.
func verify(header string, body []byte) bool {
var ts, sig string
for _, part := range strings.Split(header, ",") {
k, v, _ := strings.Cut(part, "=")
switch k {
case "t":
ts = v
case "v1":
sig = v
}
}
t, err := strconv.ParseInt(ts, 10, 64)
if err != nil || math.Abs(float64(time.Now().Unix()-t)) > 300 {
return false // no time, or more than five minutes from now: refuse a replay
}
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(ts + "."))
mac.Write(body)
want := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(want), []byte(sig))
}
func main() {
http.HandleFunc("POST /gryphon", func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(io.LimitReader(r.Body, 1<<20))
if !verify(r.Header.Get("Gryphon-Signature"), body) {
http.Error(w, "bad signature", http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusNoContent)
// ... act on the alert in body
})
http.ListenAndServe(":8080", nil)
}
const crypto = require("crypto");
const express = require("express");
const secret = process.env.GRYPHON_WEBHOOK_SECRET;
// verify reports whether a request came from Gryphon, recently.
function verify(header, body) {
const parts = Object.fromEntries((header || "").split(",").map((p) => p.split("=")));
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (!parts.v1 || !(age <= 300)) return false;
const want = crypto.createHmac("sha256", secret).update(parts.t + ".").update(body).digest("hex");
return want.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(want), Buffer.from(parts.v1));
}
const app = express();
// The raw body: the signature is over the bytes Gryphon sent.
app.post("/gryphon", express.raw({ type: "application/json" }), (req, res) => {
if (!verify(req.get("Gryphon-Signature"), req.body)) return res.status(401).send("bad signature");
res.sendStatus(204);
const alert = JSON.parse(req.body);
// ... act on alert.event, alert.host, alert.check
});
app.listen(8080);
import hashlib, hmac, os, time
from flask import Flask, abort, request
SECRET = os.environ["GRYPHON_WEBHOOK_SECRET"].encode()
app = Flask(__name__)
def verify(header, body):
"""Whether a request came from Gryphon, recently."""
parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
try:
fresh = abs(time.time() - int(parts.get("t", ""))) <= 300
except ValueError:
fresh = False
want = hmac.new(SECRET, parts.get("t", "").encode() + b"." + body, hashlib.sha256).hexdigest()
return fresh and hmac.compare_digest(want, parts.get("v1", ""))
@app.post("/gryphon")
def gryphon():
if not verify(request.headers.get("Gryphon-Signature", ""), request.get_data()):
abort(401)
alert = request.get_json()
# ... act on alert["event"], alert["host"], alert["check"]
return "", 204
4Envoyer un test
Appuyez sur Tester sur le webhook. Votre récepteur reçoit une requête dont l’event est test, et Gryphon affiche la réponse de votre récepteur. Un test n’est tenté qu’une fois ; la réponse revient donc en moins de dix secondes.
Ce qui arrive
Content-Type: application/json
User-Agent: Gryphon-Webhook/1
Gryphon-Event: problem
Gryphon-Delivery: 6f1c0d2e-4b1a-4c55-9a2e-0d0c6b9f3a11
Gryphon-Signature: t=1791310863,v1=5b2c...
{
"version": 1,
"event": "problem",
"delivery_id": "6f1c0d2e-4b1a-4c55-9a2e-0d0c6b9f3a11",
"occurred_at": "2026-10-06T18:21:03Z",
"account_id": 12,
"event_id": 98123,
"title": "Problem: HTTPS on web-01",
"host": { "id": 7, "name": "web-01", "url": "https://.../admin/host/7#services" },
"check": { "id": 311, "name": "HTTPS", "status": "problem", "previous_status": "healthy" },
"message": "Down from every location: connection refused",
"acknowledged_by": null
}
event(et l’en-têteGryphon-Event) vautproblem,warning,recovered,acknowledgedoutest.check.statusvautproblem,warningouhealthy.messageest ce que la vérification a dit : la raison, les régions d’où le service est en panne, les jours qui restent à un certificat.- Pour une prise en charge,
acknowledged_byest le nom de la personne etevent_idest le problème qu’elle a pris en charge. Un test n’a nihostnicheck. versionvaut1. Un changement qui casserait un récepteur sera une nouvelle version, jamais une modification de celle-ci. Ignorez les champs que vous ne connaissez pas.
Comment c’est livré
- Répondez par n’importe quel 2xx, en moins de dix secondes. Faites le travail lent après avoir répondu.
- Nouvelles tentatives. Une alerte est tentée jusqu’à trois fois : tout de suite, deux secondes plus tard, puis dix secondes après. Un
5xx, un408, un délai dépassé ou une connexion refusée donne lieu à une nouvelle tentative ; un429attend la durée de votreRetry-After(jusqu’à quinze secondes). Tout autre4xxn’est pas retenté : il obtiendrait la même réponse. - Doublons. Chaque tentative d’une même livraison porte le même
Gryphon-Delivery; ignorez celle que vous avez déjà vue. - Ordre. Les alertes partent à mesure qu’elles surviennent, et une vérification qui oscille peut, rarement, voir son rétablissement arriver avant son problème. Triez par
occurred_atsi l’ordre compte. - Pas de redirections. Un
3xxest un échec ; donnez à Gryphon l’adresse vers laquelle il redirige. - Adresses publiques seulement. Une adresse sur un réseau privé ou sur cette machine est refusée, et l’adresse que donne le nom est revérifiée à chaque envoi de requête.
Si ça cesse de fonctionner
Quand toutes les tentatives échouent, Gryphon marque le webhook En échec sur la page Intégrations avec la réponse de votre récepteur, et envoie un courriel une seule fois aux personnes qui gèrent le compte, pas une fois par alerte. Après une semaine sans livraison réussie, il met le webhook en pause et le signale. Réparez le récepteur, envoyez un test et réactivez-le : un test qui arrive le réactive aussi.