Webhooks
Configurez des destinations webhook pour recevoir les alertes de sécurité cside via des requêtes HTTP POST avec vérification de signature HMAC.
Comment fonctionnent les webhooks
Les webhooks vous permettent de recevoir les notifications cside sous forme de requêtes HTTP POST vers une URL que vous spécifiez. Lorsqu’un déclencheur s’active, cside envoie un payload JSON à votre endpoint webhook contenant les détails de l’événement.
Vous pouvez configurer les webhooks dans trois formats :
- JSON - payload JSON standard pour les intégrations personnalisées
- Slack - pré-formaté pour les canaux Slack
- Discord - pré-formaté pour les canaux Discord
Ajouter une destination webhook
Pour ajouter un webhook comme destination dans une notification config :
- Ouvrez le tableau de bord et naviguez vers Team Settings > Notifications
- Créez une nouvelle notification config ou modifiez-en une existante
- Sous Send To, cliquez sur Add destination et sélectionnez Webhook
- Dans le panneau Configuration, remplissez :
- URL : l’URL où vous souhaitez recevoir les notifications webhook (par ex.,
https://example.com/cside/notify) - Secret : une clé secrète utilisée pour signer les requêtes webhook (utilisée pour la vérification HMAC)
- Format : choisissez JSON, Slack ou Discord
- URL : l’URL où vous souhaitez recevoir les notifications webhook (par ex.,
- Cliquez sur Save ou Save & Test
Le payload webhook
cside envoie une requête HTTP POST par événement, avec Content-Type: application/json
et un en-tête x-cside-signature (voir la vérification HMAC).
Chaque payload contient un champ event de premier niveau qui identifie le type
d’événement, un horodatage, l’équipe ou le domaine auquel l’événement appartient
et un objet data propre à l’événement.
Le champ qui contient l’horodatage varie selon l’événement : alert.created et
les événements de digest utilisent timestamp, tandis que
security_headers.changed utilise sent_at. Lisez d’abord event, puis
analysez le reste du payload selon les tableaux ci-dessous. Traitez les champs
inconnus comme facultatifs afin que de nouveaux champs ne cassent pas votre
intégration.
IDs d’événements
| ID | Envoyé par | Champ d’horodatage | Objet data |
|---|---|---|---|
alert.created | Les déclencheurs immédiats, et le déclencheur Vulnerable Script Detected en planification immédiate | timestamp | Objet d’alerte |
security_headers.changed | Security Headers Changed | sent_at | Changement d’en-têtes de sécurité |
pci_report.available | PCI Report (quotidien ou hebdomadaire) | timestamp | Rapport PCI |
csp_block_digest | Script Blocked by CSP (quotidien ou hebdomadaire) | timestamp | Digest des blocages CSP |
csp_policy_digest | CSP Policy Digest (quotidien ou hebdomadaire) | timestamp | Digest de politique CSP |
vulnerable_script_digest | Vulnerable Script Detected (quotidien ou hebdomadaire) | timestamp | Digest des vulnérabilités |
outbound_connections_pending_review | Outbound Connections Pending Review (quotidien ou hebdomadaire) | timestamp | Connexions sortantes en attente |
Seuls alert.created et security_headers.changed respectent les formats
Slack et Discord. Les événements de digest sont toujours envoyés en JSON,
quel que soit le format configuré sur la destination.
Save & Test envoie un exemple de payload alert.created avec le
trigger_type du déclencheur de la règle, y compris pour les déclencheurs qui
livrent normalement un digest.
Tous les horodatages sont des chaînes ISO 8601 en UTC. Les IDs sont des chaînes décimales.
alert.created
Envoyé pour chaque alerte levée par un déclencheur immédiat : Script Threat Detected, High-risk Vulnerability, Managed Flagged, Malicious Tag Manager, Malicious Outbound Connection et Vulnerable Script Detected lorsque la règle est configurée pour une livraison immédiate.
| Champ | Type | Description |
|---|---|---|
event | "alert.created" | L’ID de l’événement |
timestamp | string | Date d’envoi du webhook |
domain_id | string | ID du domaine sur lequel l’alerte s’est déclenchée |
domain | string | Nom d’hôte du domaine sur lequel l’alerte s’est déclenchée |
trigger_type | string | Le déclencheur qui a produit l’alerte, voir le tableau ci-dessous |
data | object | L’objet d’alerte |
destination_id | string | ID de la destination webhook qui a reçu l’événement |
Valeurs de trigger_type :
trigger_type | Déclencheur |
|---|---|
script_threat_detected_by_rules_engine | Script Threat Detected |
high_risk_vulnerability_detected | High-risk Vulnerability |
vulnerable_script_detected | Vulnerable Script Detected (planification immédiate) |
cside_managed_list_flagged | Managed Flagged |
malicious_tag_manager | Malicious Tag Manager |
malicious_outbound_connection | Malicious Outbound Connection |
Objet d’alerte
| Champ | Type | Description |
|---|---|---|
target_type | "URL" | "HASH" | "IP" | "DOMAIN" | "HOSTNAME" | "VENDOR" | Ce que contient target |
target | string | Le sujet de l’alerte : une URL de script, un hash de script, ou l’IP, le domaine ou le nom d’hôte signalé |
action | "alert" | L’action enregistrée pour l’alerte. Actuellement toujours "alert" ; traitez-la comme une chaîne pour rester compatible avec de futures actions |
ruleset_id | string | ID du ruleset qui a correspondu |
ruleset_name | string | Nom du ruleset qui a correspondu |
ruleset_description | string | Description du ruleset qui a correspondu |
script_urls | string[] | Les URLs de script qui ont déclenché l’alerte ; peut être vide |
vuln_cve_id | string | null | ID de la vulnérabilité (CVE ou GHSA) ; null pour les alertes sans lien avec une vulnérabilité |
vuln_package | string | null | Package affecté ; null pour les alertes sans lien avec une vulnérabilité |
vuln_severity | string | null | Sévérité rapportée, par exemple HIGH ; null pour les alertes sans lien avec une vulnérabilité |
vuln_reference_url | string | null | Lien vers la vulnérabilité sur osv.dev ; null en l’absence de vuln_cve_id |
Les quatre champs vuln_* ne sont renseignés que pour
vulnerable_script_detected et high_risk_vulnerability_detected. Pour tout
autre trigger_type, ils valent null.
{
"event": "alert.created",
"timestamp": "2026-09-17T10:15:30.123456Z",
"domain_id": "1897425263682514945",
"domain": "checkout.example.com",
"trigger_type": "high_risk_vulnerability_detected",
"data": {
"target_type": "URL",
"target": "https://cdn.example.com/lodash.js",
"action": "alert",
"ruleset_id": "ruleset_vulnerability_detected",
"ruleset_name": "Vulnerability Detected",
"ruleset_description": "A script with a known vulnerability was loaded",
"script_urls": ["https://cdn.example.com/lodash.js"],
"vuln_cve_id": "CVE-2024-1234",
"vuln_package": "lodash",
"vuln_severity": "HIGH",
"vuln_reference_url": "https://osv.dev/vulnerability/CVE-2024-1234"
},
"destination_id": "1897425263682514950"
}
security_headers.changed
Envoyé par Security Headers Changed lorsque les en-têtes de sécurité observés sur les pages de paiement d’un domaine diffèrent entre deux fenêtres d’observation consécutives.
| Champ | Type | Description |
|---|---|---|
event | "security_headers.changed" | L’ID de l’événement |
sent_at | string | Date d’envoi du webhook |
team_id | string | ID de l’équipe propriétaire du domaine |
data.domain_id | string | ID du domaine |
data.domain | string | Nom d’hôte du domaine |
data.checkout_paths | string[] | Les sélecteurs de pages de paiement observés |
data.changes | object[] | Une entrée par en-tête modifié |
data.changes[].header_name | string | L’en-tête, par exemple content-security-policy |
data.changes[].from_value | string | La valeur précédente ; vide lorsque l’en-tête a été ajouté |
data.changes[].to_value | string | La nouvelle valeur ; vide lorsque l’en-tête a disparu |
data.changes[].from_date | string | Début de la fenêtre d’observation où la valeur précédente a été vue |
data.changes[].to_date | string | Début de la fenêtre d’observation où la nouvelle valeur a été vue |
pci_report.available
Envoyé par PCI Report lorsqu’une génération de rapport quotidienne ou hebdomadaire a été mise en file d’attente pour les domaines concernés qui ont des chemins de paiement configurés.
| Champ | Type | Description |
|---|---|---|
event | "pci_report.available" | L’ID de l’événement |
timestamp | string | Date d’envoi du webhook |
organization_id | string | ID de l’organisation |
team_id | string | ID de l’équipe |
data.domain_ids | string[] | IDs des domaines couverts par le rapport |
data.domains | string[] | Noms d’hôte des domaines couverts par le rapport, dans le même ordre |
data.starting_date | string | Début de la période de rapport |
data.ending_date | string | Fin de la période de rapport |
data.period | "daily" | "weekly" | La planification de la règle |
csp_block_digest
Envoyé par Script Blocked by CSP avec les rapports de violation de Content Security Policy collectés sur la période. Rien n’est envoyé s’il n’y a eu aucune violation.
| Champ | Type | Description |
|---|---|---|
event | "csp_block_digest" | L’ID de l’événement |
timestamp | string | Date d’envoi du webhook |
organization_id | string | ID de l’organisation |
team_id | string | ID de l’équipe |
data.period | "daily" | "weekly" | La planification de la règle |
data.start_date | string | Début de la période du digest |
data.end_date | string | Fin de la période du digest |
data.domains | object[] | Compteurs par domaine, triés par nombre de violations |
data.domains[].domain_id | string | ID du domaine |
data.domains[].domain | string | Nom d’hôte du domaine |
data.domains[].violation_count | number | Violations enregistrées pour le domaine |
data.top_blocked_uris | string[] | Les URIs les plus fréquemment bloquées |
data.top_violated_directives | string[] | Les directives les plus fréquemment violées |
data.total_violations | number | Total des violations sur tous les domaines concernés |
csp_policy_digest
Envoyé par CSP Policy Digest pour chaque domaine concerné dont la Content Security Policy recommandée a changé sur la période. Rien n’est envoyé si aucun domaine n’a changé.
| Champ | Type | Description |
|---|---|---|
event | "csp_policy_digest" | L’ID de l’événement |
timestamp | string | Date d’envoi du webhook |
organization_id | string | ID de l’organisation |
team_id | string | ID de l’équipe |
data.period | "daily" | "weekly" | La planification de la règle |
data.domains | object[] | Une entrée par domaine dont la politique a changé |
data.domains[].domain | string | Nom d’hôte du domaine |
data.domains[].domain_id | string | ID du domaine |
data.domains[].added | string[] | Hôtes ajoutés à la politique recommandée |
data.domains[].removed | string[] | Hôtes retirés de la politique recommandée |
data.domains[].flagged | string[] | Hôtes retirés qui figurent sur la liste de menaces gérée par cside |
data.domains[].host_count | number | Nombre d’hôtes dans la politique recommandée |
data.domains[].policy_header | string | La valeur complète de l’en-tête Content-Security-Policy recommandé |
vulnerable_script_digest
Envoyé par Vulnerable Script Detected en planification quotidienne ou hebdomadaire avec les vulnérabilités courantes (hors haut risque) détectées sur la période. Rien n’est envoyé s’il n’y a eu aucune détection.
| Champ | Type | Description |
|---|---|---|
event | "vulnerable_script_digest" | L’ID de l’événement |
timestamp | string | Date d’envoi du webhook |
organization_id | string | ID de l’organisation |
team_id | string | ID de l’équipe |
data.period | "daily" | "weekly" | La planification de la règle |
data.start_date | string | Début de la période du digest |
data.end_date | string | Fin de la période du digest |
data.domains | object[] | Compteurs par domaine |
data.domains[].domain_id | string | ID du domaine |
data.domains[].domain | string | Nom d’hôte du domaine |
data.domains[].vulnerability_count | number | Détections enregistrées pour le domaine |
data.top_vulnerabilities | object[] | Les détections les plus fréquentes |
data.top_vulnerabilities[].vulnerability_id | string | ID de la vulnérabilité (CVE ou GHSA) |
data.top_vulnerabilities[].package | string | Package affecté |
data.top_vulnerabilities[].severity | string | Sévérité rapportée |
data.top_vulnerabilities[].occurrence_count | number | Nombre d’occurrences de la détection |
data.total_vulnerabilities | number | Total des détections sur tous les domaines concernés |
outbound_connections_pending_review
Envoyé par Outbound Connections Pending Review avec le nombre de noms d’hôte sortants que votre équipe n’a pas encore approuvés dans l’inventaire des ressources. Rien n’est envoyé si rien n’est en attente.
| Champ | Type | Description |
|---|---|---|
event | "outbound_connections_pending_review" | L’ID de l’événement |
timestamp | string | Date d’envoi du webhook |
organization_id | string | ID de l’organisation |
team_id | string | ID de l’équipe |
data.period | "daily" | "weekly" | La planification de la règle |
data.pending_hostname_count | number | Noms d’hôte sortants distincts en attente de revue |
data.domain_count | number | Nombre de domaines concernés sur lesquels ils ont été observés |
Vérification HMAC
Chaque requête webhook inclut un en-tête x-cside-signature contenant un hash HMAC SHA256 du corps de la requête, signé avec le secret que vous avez fourni lors de la création de la destination webhook.
Vous devriez toujours vérifier cette signature pour confirmer que la requête provient de cside et prévenir les attaques par rejeu.
Comment vérifier la signature
Si vous utilisez JavaScript, utilisez notre package JavaScript pour une vérification intégrée.
Pour les autres langages, calculez le hash HMAC SHA256 du corps brut de la requête en utilisant votre secret webhook, puis comparez-le à la valeur de l’en-tête x-cside-signature.
async function verifyHmac(body: string, signature: string, secret: string) {
const encoder = new TextEncoder();
const encodedBody = encoder.encode(body);
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(secret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signatureBuffer = await crypto.subtle.sign("HMAC", key, encodedBody);
const finalSig = Array.from(new Uint8Array(signatureBuffer))
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("");
return signature.toLowerCase() === finalSig;
}fn verify_hmac(body: &str, signature: &str, secret: &str) -> Result<(), &'static str> {
let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).map_err(|_| "Invalid secret")?;
mac.update(body.as_bytes());
let result = mac.finalize();
let code_bytes = result.into_bytes();
let final_sig = hex::encode(code_bytes);
if signature.to_lowercase() == final_sig {
Ok(())
} else {
Err("Invalid signature")
}
}
// Add to Cargo.toml:
// [dependencies]
// hmac = "0.12"
// sha2 = "0.10"
// hex = "0.4"package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strings"
)
func verifyHmac(body, signature, secret string) bool {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(body))
finalSig := hex.EncodeToString(h.Sum(nil))
return strings.ToLower(signature) == finalSig
}defmodule HmacVerifier do
def verify_hmac(body, signature, secret) do
final_sig =
:crypto.mac(:hmac, :sha256, secret, body)
|> Base.encode16(case: :lower)
String.downcase(signature) == final_sig
end
endimport hmac
import hashlib
import secrets
def verify_hmac(body: str, signature: str, secret: str) -> bool:
final_sig = hmac.new(
secret.encode('utf-8'),
body.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signature.lower() == final_sigThanks for your feedback!