Webhooks et intégration SIEM
Diffusez les événements d'incidents et de politiques Zeuslock vers votre SIEM, votre chat ou votre outil d'astreinte avec des webhooks signés et des connecteurs prêts à l'emploi.
Ce que Zeuslock envoie, et quand
Zeuslock émet deux familles d'événements vers les systèmes externes : les événements d'incident (une action utilisateur qui a déclenché une politique) et les événements de politique (une modification effectuée par un opérateur dans la console). Les webhooks sont des requêtes HTTPS POST avec un corps JSON et un en-tête de signature HMAC-SHA256. Chaque endpoint configuré ne reçoit que les types d'événements auxquels vous vous abonnez, avec un filtrage optionnel par sévérité, type de détection ou groupe d'utilisateurs.
Les types d'événements actuellement émis sont les suivants :
incident.created— une nouvelle détection vient d'être levée par l'extension, l'agent desktop ou la CLI.incident.status_changed— un opérateur a changé l'état de l'incident (ouvert, en cours d'investigation, etc.).incident.resolved— l'incident a été clôturé.incident.false_positive— un opérateur a marqué la détection comme faux positif ; utile pour réentraîner vos propres tableaux de bord.policy.changed— une politique de détection ou de réponse a été modifiée et publiée.policy.dry_run_completed— un backtest à blanc s'est terminé ; le payload contient le diff entre politique actuelle et politique proposée.
Configurer un endpoint
Ouvrez Console Opérateur → Paramètres → Webhooks → Ajouter un endpoint. Les champs demandés sont :
- URL — obligatoirement HTTPS. Les certificats auto-signés sont rejetés.
- Secret — Zeuslock génère une chaîne hexadécimale aléatoire de 32 octets. Copiez-la immédiatement ; elle n'est affichée qu'une seule fois et n'est stockée que sous forme de hachage.
- Types d'événements — cochez les événements souhaités. Pour un usage SOC, nous recommandons au minimum
incident.createdetincident.status_changed. - Filtres (optionnels) — restreignez par
severity(low,medium,high,critical),finding_type(par exempleaws_access_key,credit_card) ougroup(n'importe quel groupe Okta/Azure AD synchronisé via SCIM).
Créez un endpoint par système aval. Mélanger Slack et Splunk sur la même URL rend l'analyse des relivraisons et la rotation du secret très pénibles par la suite.
Structure du payload
Tous les événements partagent la même enveloppe : event, event_id, delivered_at et un objet typé dont la clé correspond à la famille d'événement (incident ou policy). Exemple pour incident.created :
{
"event": "incident.created",
"event_id": "evt_01HX9F4A1Y7QZ2C8K3J5W6N0PD",
"delivered_at": "2026-05-17T14:23:11Z",
"incident": {
"id": "inc_01HX9F4A1Y7QZ2C8K3J5W6N0PD",
"severity": "high",
"finding_type": "aws_access_key",
"user": {
"email": "alice@acme.com",
"group": "engineering"
},
"destination": "chat.openai.com",
"action": "block",
"redacted_preview": "Can you debug this AWS error? AKIA*** ..."
}
}
Référence des champs
| Champ | Type | Exemple | Notes |
|---|---|---|---|
event | string | incident.created | Nom d'événement pointé. Stable d'une version à l'autre. |
event_id | string (ULID) | evt_01HX9F4A1Y... | Unique par tentative de livraison. Sert de clé de déduplication. |
delivered_at | string (RFC 3339) | 2026-05-17T14:23:11Z | UTC. Peut différer de la date de création de l'incident en cas de relivraison. |
incident.id | string (ULID) | inc_01HX9F4A1Y... | Identifiant stable. Identique pour tous les événements d'un même incident. |
incident.severity | enum | high | Parmi low, medium, high, critical. |
incident.finding_type | string | aws_access_key | Correspond au slug du détecteur ; voir la référence des détecteurs. |
incident.user.email | string | alice@acme.com | Provient du SSO, jamais saisi par l'utilisateur. |
incident.user.group | string | engineering | Nom de groupe SCIM, le cas échéant. |
incident.destination | string | chat.openai.com | Nom d'hôte de l'outil d'IA visé par le prompt. |
incident.action | enum | block | Parmi monitor, anonymize, block. |
incident.redacted_preview | string | ... AKIA*** ... | Toujours censuré côté serveur. Le secret brut ne quitte jamais l'endpoint. |
Vérifier la signature HMAC
Chaque requête porte un en-tête X-Zeuslock-Signature de la forme sha256=<hex>. Le condensat est HMAC-SHA256(secret, raw_body), calculé sur les octets exacts du corps de la requête — ne resérialisez pas. Rejetez tout ce qui ne correspond pas, et rejetez tout ce qui dépasse cinq minutes en comparant delivered_at à votre horloge.
Python
import hmac, hashlib
from datetime import datetime, timezone
SECRET = b"votre-secret-hex-32-octets"
def verifier(raw_body: bytes, signature_header: str, delivered_at: str) -> bool:
if not signature_header.startswith("sha256="):
return False
attendu = hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
recu = signature_header.split("=", 1)[1]
if not hmac.compare_digest(attendu, recu):
return False
envoye = datetime.fromisoformat(delivered_at.replace("Z", "+00:00"))
return abs((datetime.now(timezone.utc) - envoye).total_seconds()) < 300
Node.js
import crypto from "node:crypto";
const SECRET = "votre-secret-hex-32-octets";
export function verifier(rawBody, signatureHeader, deliveredAt) {
if (!signatureHeader?.startsWith("sha256=")) return false;
const attendu = crypto
.createHmac("sha256", SECRET)
.update(rawBody)
.digest("hex");
const recu = signatureHeader.slice("sha256=".length);
const a = Buffer.from(attendu, "hex");
const b = Buffer.from(recu, "hex");
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return false;
const ecart = Math.abs(Date.now() - Date.parse(deliveredAt));
return ecart < 5 * 60 * 1000;
}
Go
package webhook
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strings"
"time"
)
var secret = []byte("votre-secret-hex-32-octets")
func Verifier(rawBody []byte, sigHeader, deliveredAt string) bool {
if !strings.HasPrefix(sigHeader, "sha256=") {
return false
}
mac := hmac.New(sha256.New, secret)
mac.Write(rawBody)
attendu := mac.Sum(nil)
recu, err := hex.DecodeString(strings.TrimPrefix(sigHeader, "sha256="))
if err != nil || !hmac.Equal(attendu, recu) {
return false
}
envoye, err := time.Parse(time.RFC3339, deliveredAt)
if err != nil {
return false
}
return time.Since(envoye).Abs() < 5*time.Minute
}
Utilisez toujours une comparaison à temps constant (hmac.compare_digest, crypto.timingSafeEqual, hmac.Equal). Un simple == laisse fuiter le secret au fil du temps via une analyse temporelle.
Relivraisons et idempotence
Si votre endpoint renvoie autre chose qu'un code 2xx, ou si le délai d'attente de 10 secondes est dépassé, Zeuslock réessaie selon ce backoff : 1 minute, 5 minutes, 30 minutes, 2 heures, 12 heures. Après cinq échecs consécutifs, l'endpoint est automatiquement suspendu, une bannière apparaît dans la Console Opérateur, et une alerte Slack est envoyée à votre canal de sécurité si vous avez connecté Slack.
Chaque relivraison réutilise le même event_id. Traitez cet identifiant comme clé primaire dans votre stockage aval et vous ne compterez jamais un incident deux fois, même pendant une panne partielle. Le corps et la signature sont identiques au bit près d'une tentative à l'autre.
Intégrations directes
Vous n'avez pas besoin d'un récepteur sur mesure pour les cibles courantes. Chacune des intégrations suivantes se configure sous Paramètres → Intégrations :
- Slack — installation OAuth, choix du canal, filtre de sévérité optionnel. Une configuration courante consiste à n'envoyer que les sévérités
highetcriticaldans#alertes-securiteet le reste dans#zeuslock-firehose. - Microsoft Teams — collez une URL de webhook entrant. Utilisez un canal dédié ; le connecteur publie des cartes adaptatives qui peuvent saturer un canal général.
- PagerDuty — clé de routage Events API v2. La sévérité est mappée directement :
criticaldéclenche un page,highouvre un incident sans page, les sévérités inférieures sont ignorées. - Splunk HEC — URL HEC et token, sourcetype
zeuslock:incident. Les événements arrivent en JSON ; construisez vos tableaux de bord à partir definding_typeetuser.group. - Microsoft Sentinel — ID du workspace Log Analytics et clé partagée. La table de log personnalisée est
Zeuslock_Incidents_CL. Des requêtes de hunting et un modèle de règle analytique sont livrés avec le connecteur. - Adaptateur webhook générique — pour tout le reste (Datadog, Elastic, Chronicle, bus interne). Même enveloppe signée que ci-dessus.
Tester votre endpoint
Utilisez le bouton Envoyer un événement de test à côté de chaque endpoint dans la console. Il déclenche un incident.created synthétique avec un event_id préfixé evt_test_ pour que vous puissiez le filtrer hors de vos tableaux de production.
Si vous préférez automatiser depuis votre CI, appelez l'API REST avec un token d'API opérateur :
curl -X POST \
-H "Authorization: Bearer $ZEUSLOCK_API_TOKEN" \
https://api.zeuslock.ai/v1/webhooks/wh_01HX.../test
Échecs courants : erreurs de handshake TLS (vérifiez votre chaîne de certificats), 401 renvoyé par votre propre proxy d'authentification (ajoutez les IP de sortie Zeuslock documentées dans la console à la liste d'autorisation), et signatures qui ne correspondent pas quand un framework réécrit le corps de la requête. Si votre framework parse le JSON avant votre handler, capturez d'abord les octets bruts ou la vérification HMAC échouera.