Webhooks e integración con SIEM
Transmita los eventos de incidentes y políticas de Zeuslock a su SIEM, chat o herramienta de guardia mediante webhooks firmados y conectores listos para Slack, Splunk, Sentinel y PagerDuty.
Qué envía Zeuslock y cuándo
Zeuslock emite dos familias de eventos a sistemas externos: eventos de incidente (algo que un usuario hizo y que activó una política) y eventos de política (algo que un operador modificó en la consola). Los webhooks son peticiones HTTPS POST con cuerpo JSON y una cabecera de firma HMAC-SHA256. Cada endpoint que configure recibirá únicamente los tipos de evento a los que se haya suscrito, con filtrado opcional por severidad, tipo de detección o grupo de usuarios.
Los tipos de evento que se emiten actualmente son:
incident.created— la extensión, el agente de escritorio o la CLI acaba de generar una nueva detección.incident.status_changed— un operador ha cambiado el estado del incidente (abierto, en investigación, etc.).incident.resolved— el incidente se ha cerrado.incident.false_positive— un operador ha marcado la detección como falso positivo; útil para reentrenar sus propios cuadros de mando.policy.changed— se ha editado y publicado una política de detección o de respuesta.policy.dry_run_completed— ha terminado un backtest en seco; el payload incluye el diff entre la política actual y la propuesta.
Configurar un endpoint
Abra Consola del Operador → Ajustes → Webhooks → Añadir endpoint. Se le pedirán los siguientes campos:
- URL — debe ser HTTPS. Los certificados autofirmados se rechazan.
- Secreto — Zeuslock genera una cadena hexadecimal aleatoria de 32 bytes. Cópiela en ese momento; solo se muestra una vez y se almacena únicamente como hash.
- Tipos de evento — marque los eventos que desee. Para uso en un SOC recomendamos como mínimo
incident.createdyincident.status_changed. - Filtros (opcionales) — restrinja por
severity(low,medium,high,critical),finding_type(por ejemploaws_access_key,credit_card) ogroup(cualquier grupo de Okta/Azure AD sincronizado vía SCIM).
Cree un endpoint por cada sistema de destino. Mezclar Slack y Splunk en la misma URL complica enormemente el análisis de reenvíos y la rotación del secreto más adelante.
Forma del payload
Todos los eventos usan el mismo sobre: event, event_id, delivered_at y un objeto tipado cuya clave coincide con la familia del evento (incident o policy). Ejemplo para 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*** ..."
}
}
Referencia de campos
| Campo | Tipo | Ejemplo | Notas |
|---|---|---|---|
event | string | incident.created | Nombre de evento con punto. Estable entre versiones. |
event_id | string (ULID) | evt_01HX9F4A1Y... | Único por grupo de intentos de entrega. Úselo como clave de deduplicación. |
delivered_at | string (RFC 3339) | 2026-05-17T14:23:11Z | UTC. Puede diferir de la hora de creación del incidente en los reenvíos. |
incident.id | string (ULID) | inc_01HX9F4A1Y... | Identificador estable. Idéntico en todos los eventos de un mismo incidente. |
incident.severity | enum | high | Uno de low, medium, high, critical. |
incident.finding_type | string | aws_access_key | Coincide con el slug del detector; véase la referencia de detectores. |
incident.user.email | string | alice@acme.com | Proviene del SSO, nunca se introduce manualmente. |
incident.user.group | string | engineering | Nombre de grupo SCIM, si existe. |
incident.destination | string | chat.openai.com | Hostname de la herramienta de IA a la que iba dirigido el prompt. |
incident.action | enum | block | Uno de monitor, anonymize, block. |
incident.redacted_preview | string | ... AKIA*** ... | Siempre redactado en el servidor. El secreto en bruto nunca abandona el endpoint. |
Verificar la firma HMAC
Cada petición lleva una cabecera X-Zeuslock-Signature con la forma sha256=<hex>. El resumen es HMAC-SHA256(secreto, raw_body), calculado sobre los bytes exactos del cuerpo de la petición — no vuelva a serializarlo. Rechace cualquier cosa que no coincida, y rechace también cualquier evento con más de cinco minutos comparando delivered_at con su reloj.
Python
import hmac, hashlib
from datetime import datetime, timezone
SECRETO = b"su-secreto-hex-de-32-bytes"
def verificar(raw_body: bytes, signature_header: str, delivered_at: str) -> bool:
if not signature_header.startswith("sha256="):
return False
esperado = hmac.new(SECRETO, raw_body, hashlib.sha256).hexdigest()
recibido = signature_header.split("=", 1)[1]
if not hmac.compare_digest(esperado, recibido):
return False
enviado = datetime.fromisoformat(delivered_at.replace("Z", "+00:00"))
return abs((datetime.now(timezone.utc) - enviado).total_seconds()) < 300
Node.js
import crypto from "node:crypto";
const SECRETO = "su-secreto-hex-de-32-bytes";
export function verificar(rawBody, signatureHeader, deliveredAt) {
if (!signatureHeader?.startsWith("sha256=")) return false;
const esperado = crypto
.createHmac("sha256", SECRETO)
.update(rawBody)
.digest("hex");
const recibido = signatureHeader.slice("sha256=".length);
const a = Buffer.from(esperado, "hex");
const b = Buffer.from(recibido, "hex");
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return false;
const desfase = Math.abs(Date.now() - Date.parse(deliveredAt));
return desfase < 5 * 60 * 1000;
}
Go
package webhook
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strings"
"time"
)
var secreto = []byte("su-secreto-hex-de-32-bytes")
func Verificar(rawBody []byte, sigHeader, deliveredAt string) bool {
if !strings.HasPrefix(sigHeader, "sha256=") {
return false
}
mac := hmac.New(sha256.New, secreto)
mac.Write(rawBody)
esperado := mac.Sum(nil)
recibido, err := hex.DecodeString(strings.TrimPrefix(sigHeader, "sha256="))
if err != nil || !hmac.Equal(esperado, recibido) {
return false
}
enviado, err := time.Parse(time.RFC3339, deliveredAt)
if err != nil {
return false
}
return time.Since(enviado).Abs() < 5*time.Minute
}
Utilice siempre una comparación en tiempo constante (hmac.compare_digest, crypto.timingSafeEqual, hmac.Equal). Un simple == filtra el secreto con el tiempo mediante análisis temporal.
Reintentos e idempotencia
Si su endpoint devuelve algo distinto de un 2xx, o agota el tiempo de espera de 10 segundos, Zeuslock reintenta con este backoff: 1 minuto, 5 minutos, 30 minutos, 2 horas, 12 horas. Tras cinco fallos consecutivos, el endpoint se suspende automáticamente, aparece un banner en la Consola del Operador y se envía una alerta a Slack en su canal de seguridad si tiene Slack conectado.
Cada reenvío reutiliza el mismo event_id. Trate ese identificador como clave primaria en su almacén descendente y nunca contará un incidente dos veces, ni siquiera durante una caída parcial. El cuerpo y la firma son idénticos byte a byte entre reintentos.
Integraciones directas
No necesita un receptor a medida para los destinos habituales. Todas las siguientes se configuran en Ajustes → Integraciones:
- Slack — instalación OAuth, elección del canal, filtro de severidad opcional. Una configuración común es enviar solo
highycriticala#alertas-seguridady el resto a#zeuslock-firehose. - Microsoft Teams — pegue una URL de webhook entrante. Use un canal dedicado; el conector publica adaptive cards que pueden saturar un canal general.
- PagerDuty — routing key de Events API v2. La severidad se mapea directamente:
criticalactiva una llamada,highcrea un incidente sin notificación, las severidades menores se descartan. - Splunk HEC — URL HEC y token, sourcetype
zeuslock:incident. Los eventos llegan en JSON; construya dashboards a partir definding_typeyuser.group. - Microsoft Sentinel — workspace ID de Log Analytics y shared key. La tabla de log personalizada es
Zeuslock_Incidents_CL. El conector incluye consultas de hunting y una plantilla de regla analítica. - Adaptador de webhook genérico — para todo lo demás (Datadog, Elastic, Chronicle, bus interno). Mismo sobre firmado que los anteriores.
Probar su endpoint
Use el botón Enviar evento de prueba junto a cada endpoint en la consola. Dispara un incident.created sintético con un event_id que comienza por evt_test_, de modo que pueda excluirlo de sus dashboards de producción.
Si prefiere automatizarlo desde su CI, llame a la API REST con un token de API de operador:
curl -X POST \
-H "Authorization: Bearer $ZEUSLOCK_API_TOKEN" \
https://api.zeuslock.ai/v1/webhooks/wh_01HX.../test
Fallos habituales: errores de handshake TLS (revise su cadena de certificados), 401 desde su propio proxy de autenticación (añada las IP de salida de Zeuslock documentadas en la consola a la lista de permitidos) y desajustes de firma cuando un framework reescribe el cuerpo de la petición. Si su framework parsea el JSON antes de llegar a su handler, capture primero los bytes en bruto o la verificación HMAC fallará.