Passer au contenu

Recevoir les alertes par un webhook signé

Faites envoyer chaque alerte par Gryphon à votre propre point d’accès HTTPS, en JSON, avec une signature qui prouve qu’elle vient de Gryphon : le contenu, les en-têtes et la façon de vérifier la signature.

Intégration que vous ajouterez
Webhook
L’agent
Aucun agent requis
Écrit pour
Tout système

Ce que vous obtiendrez

  • Un POST vers 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 POST et 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.

Ajouter un webhook Intégrations → Ajouter une intégration
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.

Go
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)
}
Node.js (Express)
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);
Python (Flask)
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

POST /gryphon
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ête Gryphon-Event) vaut problem, warning, recovered, acknowledged ou test.
  • check.status vaut problem, warning ou healthy. message est 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_by est le nom de la personne et event_id est le problème qu’elle a pris en charge. Un test n’a ni host ni check.
  • version vaut 1. 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, un 408, un délai dépassé ou une connexion refusée donne lieu à une nouvelle tentative ; un 429 attend la durée de votre Retry-After (jusqu’à quinze secondes). Tout autre 4xx n’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_at si l’ordre compte.
  • Pas de redirections. Un 3xx est 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.

Ce n’est pas ce que vous exploitez ? Parcourez tous les guides, ou dites-nous ce que vous devez surveiller et nous rédigerons le guide.

Quatorze jours gratuits. Puis à partir de $4.99 par mois.

L’agent, le tableau de bord, les applications et toutes les vérifications sauf les cinq pour Kubernetes font partie de chaque forfait. Les forfaits diffèrent par la quantité que vous surveillez, la fréquence, la provenance des vérifications, et le nombre de personnes et de pages d’état qu’ils comprennent. Comparer les forfaits. Annulez quand vous voulez.