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
Pour créer des tickets Jira à partir des webhooks, quel que soit votre plan, consultez Jira Automation.
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
cside signe chaque requête avec l’en-tête x-cside-signature, mais ne peut pas ajouter d’en-têtes personnalisés. Si votre récepteur s’authentifie avec son propre en-tête, vérifiez s’il accepte aussi le token dans l’URL, comme le fait Jira Automation.
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.
Chaque payload JSON contient aussi deux champs lisibles par un humain, summary
(un titre sur une ligne) et description (des détails sur plusieurs lignes), à
la fois au premier niveau et répétés dans data. Utilisez-les pour remplir un
ticket ou un message de chat sans construire votre propre texte pour chaque
événement. Ils sont répétés dans data parce que
Jira Automation traite de
façon particulière une clé data de premier niveau.
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, security_headers.changed et pci_report.available
respectent les formats Slack et Discord. Les autres é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. Son summary commence par [Test]. Security
Headers Changed envoie à la place un exemple de payload
security_headers.changed. Si votre endpoint répond par une erreur, le résultat
du test affiche le code de statut HTTP qu’il a renvoyé.
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 |
summary | string | Titre sur une ligne, par exemple [cside] Malicious Script: {ruleset} on {domain}. Le même texte que l’intégration Jira utilise pour le champ Summary du ticket |
description | string | Explication de l’alerte en texte brut |
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 |
|---|---|---|
summary | string | Identique au champ summary de premier niveau |
description | string | Identique au champ description de premier niveau |
domain | string | Identique au champ domain de premier niveau |
trigger_type | string | Identique au champ trigger_type de premier niveau |
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",
"summary": "[cside] High-risk Vulnerability: CVE-2024-1234 on checkout.example.com",
"description": "cside detected a high-risk vulnerability on checkout.example.com: lodash. Severity: High.\n\nThis is a high-risk finding (malware or a high-severity vulnerability). Investigate it promptly: if it is malware, remove the script immediately and rotate any credentials it may have accessed; otherwise prioritize updating or removing the affected dependency.",
"data": {
"summary": "[cside] High-risk Vulnerability: CVE-2024-1234 on checkout.example.com",
"description": "cside detected a high-risk vulnerability on checkout.example.com: lodash. Severity: High.\n\nThis is a high-risk finding (malware or a high-severity vulnerability). Investigate it promptly: if it is malware, remove the script immediately and rotate any credentials it may have accessed; otherwise prioritize updating or removing the affected dependency.",
"domain": "checkout.example.com",
"trigger_type": "high_risk_vulnerability_detected",
"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 |
summary | string | [cside] Security headers changed on {domain} |
description | string | Une ligne par en-tête modifié : - {header}: {from} -> {to} |
data.summary | string | Identique à summary |
data.description | string | Identique à description |
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 une fois le rapport de la période prêt, pour les
domaines concernés qui ont des chemins de paiement configurés. Il est envoyé une
seule fois par rapport ; une livraison rejetée n’est pas retentée. Chaque requête
contient aussi un en-tête x-cside-delivery-id qui reste identique si le même
rapport est livré à nouveau, ce qui vous permet de dédupliquer sur cette valeur.
| 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 |
summary | string | [cside / PCI DSS]: New PCI Compliance Report for {domains} (les trois premiers domaines, puis « and N more ») |
description | string | Une phrase qui nomme les domaines et les dates, suivie du lien vers le rapport |
data.summary | string | Identique à summary |
data.description | string | Identique à description |
data.report_id | string | ID du rapport |
data.report_url | string | Lien pour télécharger le rapport depuis le tableau de bord cside |
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" | "custom" | La durée de la période de rapport. custom est utilisé pour un rapport couvrant toute autre plage |
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 |
summary | string | Titre du digest sur une ligne |
description | string | Détail du digest sur plusieurs lignes |
data.summary | string | Identique à summary |
data.description | string | Identique à description |
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 |
summary | string | Titre du digest sur une ligne |
description | string | Détail du digest sur plusieurs lignes |
data.summary | string | Identique à summary |
data.description | string | Identique à description |
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 |
summary | string | Titre du digest sur une ligne |
description | string | Détail du digest sur plusieurs lignes |
data.summary | string | Identique à summary |
data.description | string | Identique à description |
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 |
summary | string | Titre du digest sur une ligne |
description | string | Détail du digest sur plusieurs lignes |
data.summary | string | Identique à summary |
data.description | string | Identique à description |
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!