Un webhook envoie les décisions bot et bot_high de Bot Protection à un endpoint à vous : le relais d'un canal de discussion, une file de tickets, votre propre revue de fraude. Votre serveur reçoit toujours chaque décision de /v1/decide comme avant ; le webhook en est une copie, envoyée après la réponse, et ne fait jamais attendre une décision.
Définir votre endpoint
Sur la page Configuration du tableau de bord, sous Webhook, un propriétaire écrit l'adresse de l'endpoint et choisit ce qu'il reçoit :
- Chaque décision des classes choisies,
bot,bot_highou les deux, par lots de 100 au plus, quelques secondes après la décision. - Un résumé toutes les 5, 15 ou 60 minutes : combien de décisions
botetbot_highil y a eu.
L'adresse est en https://, sur le port 443, avec un nom comme hooks.example.com, jamais une adresse IP. Korzen n'envoie rien à une adresse d'un réseau privé, quelle que soit l'adresse du nom, et le vérifie à chaque livraison.
La première fois qu'un endpoint est enregistré, Configuration affiche son secret de signature, kz_whs_ et 64 caractères, une seule fois : copiez-le dans la configuration de votre endpoint. Créer un nouveau secret le remplace, affiché une seule fois lui aussi ; pendant 24 heures, l'ancien signe encore chaque livraison à côté, pour que votre endpoint continue de vérifier pendant que vous mettez sa copie à jour. Envoyer un test envoie maintenant une livraison test signée et affiche ce que votre endpoint a répondu.
Le payload
Chaque livraison est un POST avec un corps JSON. Un lot de décisions :
{
"type": "decisions",
"id": "0193a5f0-7c1e-7d2a-9b4e-3f1a2c9d8e01",
"decisions": [
{
"at": "2026-10-28T12:00:00.123Z",
"request_id": "…",
"class": "bot_high",
"action": "block",
"intended_action": "block",
"bot_score": 97,
"mode": "block",
"reasons": ["behaviour", "inconsistency"]
}
],
"dropped": 0
}
Chaque décision est at, le moment où elle a été prise, et ces membres de la réponse que /v1/decide a donnée à votre serveur : request_id, class, bot_score, human_score, reasons, action, intended_action, mode, step_up, token_required, identity_limited, seen_before, crawler, decoy et own_server. Rien d'autre : jamais le nom d'une règle, le context que vous avez envoyé ni le lien de la page de challenge. dropped compte les décisions pour lesquelles ce lot n'avait pas de place.
Un résumé :
{"type": "summary", "id": "…", "from": "2026-10-28T12:00:00Z", "to": "2026-10-28T12:15:00Z", "bot": 412, "bot_high": 37}
Un test : {"type": "test", "id": "…", "at": "…"}. Lisez type d'abord : un nouveau type peut venir, et un champ peut être ajouté à chacun d'eux.
id est le même à chaque tentative d'une livraison : si vous l'avez déjà traitée, répondez 2xx et ignorez-la. Les livraisons peuvent arriver dans le désordre.
Ce que promet une livraison
- Au moins une fois, une fois en file. Une livraison en file est tentée jusqu'à ce que votre endpoint réponde
2xxou soit mis en pause ; la même livraison peut donc arriver deux fois : ignorez la répétition grâce à sonid. - Au mieux avant cela. Chaque serveur de Korzen garde une décision jusqu'à 10 secondes avant de la mettre en file, et met en file tout ce qu'il garde quand il s'arrête pour un deploy. Un serveur qui plante perd ce qu'il gardait, et les décisions au-delà de la place d'un lot ou de la limite horaire de votre offre sont comptées dans
dropped, jamais envoyées. - Le webhook est une copie, pas la référence. Chaque décision reste sur la page Décisions du tableau de bord, que les outils
list_decisionsetget_decisiondu serveur MCP lisent aussi : retrouvez-y une décision par sonrequest_id.
Vérifier la signature
Chaque livraison porte Korzen-Signature: t=<secondes Unix>,v1=<hex> : le HMAC-SHA256, sous votre secret de signature, de t, d'un point et du corps exactement tel que reçu. Calculez-le sur les octets bruts, avant tout parsing JSON, comparez en temps constant, et refusez un t à plus de 5 minutes de votre horloge, pour qu'une livraison enregistrée une fois ne puisse pas être renvoyée plus tard. Pendant 24 heures après un nouveau secret, un second v1 suit, sous l'ancien : acceptez une livraison dès qu'un v1 correspond.
import { createHmac, timingSafeEqual } from "node:crypto"; // raw: the body as received (a Buffer); header: Korzen-Signature function verify(raw, header, secret) { const parts = header.split(",").map((p) => p.split("=")); const t = Number(parts.find(([k]) => k === "t")?.[1]); if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > 300) return false; const mac = createHmac("sha256", secret).update(`${t}.`).update(raw).digest(); return parts.some(([k, v]) => { const got = Buffer.from(k === "v1" ? v ?? "" : "", "hex"); return got.length === mac.length && timingSafeEqual(got, mac); }); }
import hashlib, hmac, time def verify(raw: bytes, header: str, secret: str) -> bool: parts = [p.partition("=")[::2] for p in header.split(",")] t = next((v for k, v in parts if k == "t"), "") if not t.isdigit() or abs(time.time() - int(t)) > 300: return False mac = hmac.new(secret.encode(), f"{t}.".encode() + raw, hashlib.sha256).hexdigest() return any(k == "v1" and hmac.compare_digest(mac, v) for k, v in parts)
Réponse et nouvelles tentatives
Répondez n'importe quel 2xx en 5 secondes : le corps n'est pas lu. Toute autre réponse, une redirection aussi, ou aucune réponse à temps, est un échec, et la livraison est retentée 30 secondes plus tard, puis après deux fois plus longtemps à chaque fois, 8 tentatives en une heure environ. Faites le travail lent après avoir répondu.
Tant que votre endpoint échoue, les nouvelles décisions attendent, jusqu'à 100, et les autres sont comptées dans dropped. Un endpoint qui échoue pendant une heure est mis en pause : rien ne lui est envoyé, les décisions entre-temps ne sont pas envoyées, et chaque propriétaire est prévenu par email. Configuration affiche le statut ou l'erreur de la dernière livraison ; Reprendre renvoie à partir de la prochaine décision.
Limites
Chaque offre envoie jusqu'à un nombre de décisions par heure : l'essai gratuit 1 000, Starter 10 000, Pro 50 000, Scale 200 000. Au-delà, les décisions sont comptées dans le dropped du lot suivant, et l'heure suivante repart de zéro. Un résumé est toujours envoyé. Un webhook ne compte pas dans vos décisions du mois.