Webhooks
Configure destinos de webhook para receber alertas de segurança do cside via requisições HTTP POST com verificação de assinatura HMAC.
Como os webhooks funcionam
Os webhooks permitem que você receba notificações do cside como requisições HTTP POST para uma URL que você especificar. Quando um trigger dispara, o cside envia um payload JSON para o seu endpoint de webhook contendo detalhes sobre o evento.
Você pode configurar webhooks em três formatos:
- JSON - payload JSON padrão para integrações personalizadas
- Slack - pré-formatado para canais Slack
- Discord - pré-formatado para canais Discord
Para criar itens de trabalho do Jira a partir de webhooks em qualquer plano, consulte Jira Automation.
Adicionando um destino de webhook
Para adicionar um webhook como destino em uma notification config:
- Abra o painel e navegue até Team Settings > Notifications
- Crie uma nova notification config ou edite uma existente
- Em Send To, clique em Add destination e selecione Webhook
- No painel de Configuration, preencha:
- URL: a URL onde você deseja receber as notificações de webhook (ex.:
https://example.com/cside/notify) - Secret: uma chave secreta usada para assinar as requisições de webhook (usada para verificação HMAC)
- Format: escolha JSON, Slack ou Discord
- URL: a URL onde você deseja receber as notificações de webhook (ex.:
- Clique em Save ou Save & Test
O cside assina cada requisição com o header x-cside-signature, mas não consegue adicionar headers personalizados. Se o seu receptor autentica com um header próprio, verifique se ele também aceita o token na URL, como faz o Jira Automation.
O payload do webhook
O cside envia uma requisição HTTP POST por evento, com Content-Type: application/json
e um header x-cside-signature (consulte Verificação HMAC).
Cada payload traz um campo event de nível superior que identifica o tipo do
evento, um timestamp, a equipe ou o domínio ao qual o evento pertence e um
objeto data específico do evento.
O campo que contém o timestamp varia por evento: alert.created e os eventos
de digest usam timestamp, enquanto security_headers.changed usa sent_at.
Leia event primeiro e depois interprete o restante do payload de acordo com
as tabelas abaixo. Trate campos desconhecidos como opcionais para que novos
campos não quebrem a sua integração.
Todo payload JSON também traz dois campos legíveis por humanos, summary (um
título de uma linha) e description (detalhes em várias linhas), presentes no
nível superior e repetidos dentro de data. Use-os para preencher um ticket ou
uma mensagem de chat sem precisar montar o seu próprio texto para cada evento.
Eles são repetidos dentro de data porque o
Jira Automation trata de
forma especial uma chave data de nível superior.
Event IDs
| ID | Enviado por | Campo de timestamp | Objeto data |
|---|---|---|---|
alert.created | Triggers imediatos e o trigger Vulnerable Script Detected na programação imediata | timestamp | Objeto de alerta |
security_headers.changed | Security Headers Changed | sent_at | Mudança de cabeçalhos de segurança |
pci_report.available | PCI Report (diário ou semanal) | timestamp | Relatório PCI |
csp_block_digest | Script Blocked by CSP (diário ou semanal) | timestamp | Digest de bloqueios de CSP |
csp_policy_digest | CSP Policy Digest (diário ou semanal) | timestamp | Digest de política de CSP |
vulnerable_script_digest | Vulnerable Script Detected (diário ou semanal) | timestamp | Digest de vulnerabilidades |
outbound_connections_pending_review | Outbound Connections Pending Review (diário ou semanal) | timestamp | Conexões de saída pendentes |
Somente alert.created, security_headers.changed e pci_report.available
respeitam os formatos Slack e Discord. Os outros eventos de digest são
sempre enviados como JSON, independentemente do formato configurado no destino.
Save & Test envia um payload alert.created de exemplo com o trigger_type
do trigger da regra, inclusive para triggers que normalmente entregam um digest.
O summary desse payload começa com [Test]. Security Headers Changed
envia, em vez disso, um payload security_headers.changed de exemplo. Se o seu
endpoint responder com um erro, o resultado do teste mostra o código de status
HTTP que ele retornou.
Todos os timestamps são strings ISO 8601 em UTC. Os IDs são strings decimais.
alert.created
Enviado para cada alerta gerado por um trigger imediato: Script Threat Detected, High-risk Vulnerability, Managed Flagged, Malicious Tag Manager, Malicious Outbound Connection e Vulnerable Script Detected quando a regra está configurada para entrega imediata.
| Campo | Tipo | Descrição |
|---|---|---|
event | "alert.created" | O ID do evento |
timestamp | string | Quando o webhook foi enviado |
domain_id | string | ID do domínio em que o alerta disparou |
domain | string | Nome de host do domínio em que o alerta disparou |
trigger_type | string | O trigger que gerou o alerta, veja a tabela abaixo |
summary | string | Título de uma linha, por exemplo [cside] Malicious Script: {ruleset} on {domain}. O mesmo texto que a integração do Jira usa no campo Summary da issue |
description | string | Explicação do alerta em texto simples |
data | object | O objeto de alerta |
destination_id | string | ID do destino de webhook que recebeu o evento |
Valores de trigger_type:
trigger_type | Trigger |
|---|---|
script_threat_detected_by_rules_engine | Script Threat Detected |
high_risk_vulnerability_detected | High-risk Vulnerability |
vulnerable_script_detected | Vulnerable Script Detected (programação imediata) |
cside_managed_list_flagged | Managed Flagged |
malicious_tag_manager | Malicious Tag Manager |
malicious_outbound_connection | Malicious Outbound Connection |
Objeto de alerta
| Campo | Tipo | Descrição |
|---|---|---|
summary | string | O mesmo que o summary de nível superior |
description | string | O mesmo que o description de nível superior |
domain | string | O mesmo que o domain de nível superior |
trigger_type | string | O mesmo que o trigger_type de nível superior |
target_type | "URL" | "HASH" | "IP" | "DOMAIN" | "HOSTNAME" | "VENDOR" | O que target contém |
target | string | O objeto do alerta: a URL de um script, o hash de um script, ou o IP, domínio ou nome de host sinalizado |
action | "alert" | A ação registrada para o alerta. Atualmente sempre "alert"; trate-a como uma string para manter a compatibilidade com ações futuras |
ruleset_id | string | ID do ruleset que correspondeu |
ruleset_name | string | Nome do ruleset que correspondeu |
ruleset_description | string | Descrição do ruleset que correspondeu |
script_urls | string[] | As URLs de script que dispararam o alerta; pode estar vazio |
vuln_cve_id | string | null | ID da vulnerabilidade (CVE ou GHSA); null para alertas que não são de vulnerabilidade |
vuln_package | string | null | Pacote afetado; null para alertas que não são de vulnerabilidade |
vuln_severity | string | null | Severidade reportada, como HIGH; null para alertas que não são de vulnerabilidade |
vuln_reference_url | string | null | Link para a vulnerabilidade no osv.dev; null quando não há vuln_cve_id |
Os quatro campos vuln_* só são preenchidos para vulnerable_script_detected e
high_risk_vulnerability_detected. Para qualquer outro trigger_type, eles
são 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
Enviado por Security Headers Changed quando os cabeçalhos de segurança observados nas páginas de checkout de um domínio diferem entre duas janelas de observação consecutivas.
| Campo | Tipo | Descrição |
|---|---|---|
event | "security_headers.changed" | O ID do evento |
sent_at | string | Quando o webhook foi enviado |
team_id | string | ID da equipe proprietária do domínio |
summary | string | [cside] Security headers changed on {domain} |
description | string | Uma linha por cabeçalho alterado: - {header}: {from} -> {to} |
data.summary | string | O mesmo que summary |
data.description | string | O mesmo que description |
data.domain_id | string | ID do domínio |
data.domain | string | Nome de host do domínio |
data.checkout_paths | string[] | Os seletores de página de checkout que foram observados |
data.changes | object[] | Uma entrada por cabeçalho alterado |
data.changes[].header_name | string | O cabeçalho, por exemplo content-security-policy |
data.changes[].from_value | string | O valor anterior; vazio quando o cabeçalho foi adicionado |
data.changes[].to_value | string | O novo valor; vazio quando o cabeçalho desapareceu |
data.changes[].from_date | string | Início da janela de observação em que o valor anterior foi visto |
data.changes[].to_date | string | Início da janela de observação em que o novo valor foi visto |
pci_report.available
Enviado por PCI Report assim que o relatório do período fica pronto, para
os domínios no escopo que têm caminhos de checkout configurados. Ele é enviado
uma vez por relatório; uma entrega rejeitada não é reenviada. Cada requisição
também traz um header x-cside-delivery-id que permanece o mesmo se o mesmo
relatório for entregue novamente, para que você possa deduplicar com base nele.
| Campo | Tipo | Descrição |
|---|---|---|
event | "pci_report.available" | O ID do evento |
timestamp | string | Quando o webhook foi enviado |
organization_id | string | ID da organização |
team_id | string | ID da equipe |
summary | string | [cside / PCI DSS]: New PCI Compliance Report for {domains} (os três primeiros domínios, depois “and N more”) |
description | string | Uma frase que cita os domínios e as datas, seguida do link do relatório |
data.summary | string | O mesmo que summary |
data.description | string | O mesmo que description |
data.report_id | string | ID do relatório |
data.report_url | string | Link para baixar o relatório no painel do cside |
data.domain_ids | string[] | IDs dos domínios cobertos pelo relatório |
data.domains | string[] | Nomes de host dos domínios cobertos pelo relatório, na mesma ordem |
data.starting_date | string | Início do período do relatório |
data.ending_date | string | Fim do período do relatório |
data.period | "daily" | "weekly" | "custom" | A duração do período do relatório. custom é usado para um relatório que cobre qualquer outro intervalo |
csp_block_digest
Enviado por Script Blocked by CSP com os relatórios de violação da Content Security Policy coletados no período. Nada é enviado quando não houve violações.
| Campo | Tipo | Descrição |
|---|---|---|
event | "csp_block_digest" | O ID do evento |
timestamp | string | Quando o webhook foi enviado |
organization_id | string | ID da organização |
team_id | string | ID da equipe |
summary | string | Título de uma linha para o digest |
description | string | Detalhamento do digest em várias linhas |
data.summary | string | O mesmo que summary |
data.description | string | O mesmo que description |
data.period | "daily" | "weekly" | A programação da regra |
data.start_date | string | Início do período do digest |
data.end_date | string | Fim do período do digest |
data.domains | object[] | Contagens por domínio, ordenadas pelo número de violações |
data.domains[].domain_id | string | ID do domínio |
data.domains[].domain | string | Nome de host do domínio |
data.domains[].violation_count | number | Violações registradas para o domínio |
data.top_blocked_uris | string[] | As URIs bloqueadas com mais frequência |
data.top_violated_directives | string[] | As diretivas violadas com mais frequência |
data.total_violations | number | Total de violações em todos os domínios no escopo |
csp_policy_digest
Enviado por CSP Policy Digest para cada domínio no escopo cuja Content Security Policy recomendada mudou no período. Nada é enviado quando nenhum domínio mudou.
| Campo | Tipo | Descrição |
|---|---|---|
event | "csp_policy_digest" | O ID do evento |
timestamp | string | Quando o webhook foi enviado |
organization_id | string | ID da organização |
team_id | string | ID da equipe |
summary | string | Título de uma linha para o digest |
description | string | Detalhamento do digest em várias linhas |
data.summary | string | O mesmo que summary |
data.description | string | O mesmo que description |
data.period | "daily" | "weekly" | A programação da regra |
data.domains | object[] | Uma entrada por domínio cuja política mudou |
data.domains[].domain | string | Nome de host do domínio |
data.domains[].domain_id | string | ID do domínio |
data.domains[].added | string[] | Hosts adicionados à política recomendada |
data.domains[].removed | string[] | Hosts removidos da política recomendada |
data.domains[].flagged | string[] | Hosts removidos que estão na lista gerenciada de ameaças do cside |
data.domains[].host_count | number | Número de hosts na política recomendada |
data.domains[].policy_header | string | O valor completo recomendado do header Content-Security-Policy |
vulnerable_script_digest
Enviado por Vulnerable Script Detected em uma programação diária ou semanal com as descobertas de vulnerabilidade rotineiras (não de alto risco) do período. Nada é enviado quando não houve descobertas.
| Campo | Tipo | Descrição |
|---|---|---|
event | "vulnerable_script_digest" | O ID do evento |
timestamp | string | Quando o webhook foi enviado |
organization_id | string | ID da organização |
team_id | string | ID da equipe |
summary | string | Título de uma linha para o digest |
description | string | Detalhamento do digest em várias linhas |
data.summary | string | O mesmo que summary |
data.description | string | O mesmo que description |
data.period | "daily" | "weekly" | A programação da regra |
data.start_date | string | Início do período do digest |
data.end_date | string | Fim do período do digest |
data.domains | object[] | Contagens por domínio |
data.domains[].domain_id | string | ID do domínio |
data.domains[].domain | string | Nome de host do domínio |
data.domains[].vulnerability_count | number | Descobertas registradas para o domínio |
data.top_vulnerabilities | object[] | As descobertas mais frequentes |
data.top_vulnerabilities[].vulnerability_id | string | ID da vulnerabilidade (CVE ou GHSA) |
data.top_vulnerabilities[].package | string | Pacote afetado |
data.top_vulnerabilities[].severity | string | Severidade reportada |
data.top_vulnerabilities[].occurrence_count | number | Quantas vezes a descoberta ocorreu |
data.total_vulnerabilities | number | Total de descobertas em todos os domínios no escopo |
outbound_connections_pending_review
Enviado por Outbound Connections Pending Review com o número de nomes de host de saída que a sua equipe ainda não aprovou no inventário de Resources. Nada é enviado quando não há nada pendente.
| Campo | Tipo | Descrição |
|---|---|---|
event | "outbound_connections_pending_review" | O ID do evento |
timestamp | string | Quando o webhook foi enviado |
organization_id | string | ID da organização |
team_id | string | ID da equipe |
summary | string | Título de uma linha para o digest |
description | string | Detalhamento do digest em várias linhas |
data.summary | string | O mesmo que summary |
data.description | string | O mesmo que description |
data.period | "daily" | "weekly" | A programação da regra |
data.pending_hostname_count | number | Nomes de host de saída distintos aguardando revisão |
data.domain_count | number | Número de domínios no escopo em que eles foram observados |
Verificação HMAC
Toda requisição de webhook inclui um header x-cside-signature contendo um hash HMAC SHA256 do corpo da requisição, assinado com o secret que você forneceu ao criar o destino de webhook.
Você deve sempre verificar essa assinatura para confirmar que a requisição veio do cside e prevenir ataques de replay.
Como verificar a assinatura
Se você está usando JavaScript, use nosso pacote JavaScript para verificação integrada.
Para outras linguagens, calcule o hash HMAC SHA256 do corpo bruto da requisição usando seu secret de webhook e compare-o com o valor do header 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!