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:

  1. URL — debe ser HTTPS. Los certificados autofirmados se rechazan.
  2. 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.
  3. Tipos de evento — marque los eventos que desee. Para uso en un SOC recomendamos como mínimo incident.created y incident.status_changed.
  4. Filtros (opcionales) — restrinja por severity (low, medium, high, critical), finding_type (por ejemplo aws_access_key, credit_card) o group (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

CampoTipoEjemploNotas
eventstringincident.createdNombre de evento con punto. Estable entre versiones.
event_idstring (ULID)evt_01HX9F4A1Y...Único por grupo de intentos de entrega. Úselo como clave de deduplicación.
delivered_atstring (RFC 3339)2026-05-17T14:23:11ZUTC. Puede diferir de la hora de creación del incidente en los reenvíos.
incident.idstring (ULID)inc_01HX9F4A1Y...Identificador estable. Idéntico en todos los eventos de un mismo incidente.
incident.severityenumhighUno de low, medium, high, critical.
incident.finding_typestringaws_access_keyCoincide con el slug del detector; véase la referencia de detectores.
incident.user.emailstringalice@acme.comProviene del SSO, nunca se introduce manualmente.
incident.user.groupstringengineeringNombre de grupo SCIM, si existe.
incident.destinationstringchat.openai.comHostname de la herramienta de IA a la que iba dirigido el prompt.
incident.actionenumblockUno de monitor, anonymize, block.
incident.redacted_previewstring... 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 high y critical a #alertas-seguridad y 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: critical activa una llamada, high crea 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 de finding_type y user.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á.