Ir al contenido principal

Webhooks

Los webhooks de CaptainDNS envían los eventos de tu perfil a la URL HTTP que configures. Ya no necesitas hacer polling: cada alerta y cada cambio de política llega a tu endpoint en pocos segundos, con un payload firmado que puedes verificar en el lado del receptor.

Visión general

CaptainDNS emite un POST JSON a la URL configurada cada vez que ocurre un evento en tu perfil (monitor caído, fallo de despliegue MTA-STS, etc.). Si has definido un secreto al crear el canal, la petición se firma con HMAC-SHA256 y se marca con un timestamp para que puedas rechazar replays. Cada canal se suscribe a una o varias categorías (monitoring, deployment, dns): solo los eventos de las categorías marcadas se envían a tu endpoint.

Si una entrega falla, se reintenta automáticamente hasta 6 veces con un backoff 10s, 1min, 10min, 1h, 6h, 24h. El historial de cada entrega se consulta en el dashboard (pestaña Deliveries) y las entregas en fallo permanente pueden reenviarse manualmente.

Configuración

La creación y gestión de los canales webhook se realiza en el dashboard de CaptainDNS, sección Notificaciones. Hay tres campos obligatorios:

  • URL: endpoint HTTPS accesible públicamente. Las URL HTTP se rechazan.
  • Secreto (opcional pero recomendado): cadena compartida entre CaptainDNS y tu receptor. Se usa para firmar cada petición. Sin secreto, no se envía ningún header de firma.
  • Categorías: al menos una entre monitoring, deployment, dns. El filtrado se aplica en el servidor durante el dispatch.

El número de canales activos por perfil (webhooks e integraciones Slack combinados) depende de tu plan:

PlanCanales activos
Free1
Starter3
Pro10
Business25
Enterprise100

Superada la cuota, la creación devuelve 402 webhook_quota_exceeded. Para ampliar este límite, cambia de plan o contacta con el soporte.

Las operaciones CRUD usan rutas REST dedicadas (/v1/notifications/channels/*) protegidas por la sesión de Auth0: las consume el dashboard y no están abiertas a la clave API pública.

Petición enviada

Cada evento produce una petición con el siguiente formato:

POST https://tu-endpoint.captaindns.com/ HTTP/1.1
Content-Type: application/json
User-Agent: CaptainDNS-Webhook/2.0 (+https://www.captaindns.com/es/docs/api/webhooks)
X-CaptainDNS-Event-ID: 7a3c9b1e-2f4d-4a6b-8c7d-9e1f2a3b4c5d
X-CaptainDNS-Delivery-ID: b8c1f4e2-5a6b-4c7d-8e9f-0a1b2c3d4e5f
X-CaptainDNS-Attempt: 1/6
X-CaptainDNS-Event-Type: MONITOR_DOWN
X-CaptainDNS-Signature: sha256=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
X-CaptainDNS-Timestamp: 1776550245

{"schema_version":"2","event_type":"MONITOR_DOWN","category":"monitoring", ...}

Detalles:

  • Método: POST sobre la URL exacta configurada.
  • Content-Type: siempre application/json.
  • User-Agent: CaptainDNS-Webhook/2.0 (+https://www.captaindns.com/es/docs/api/webhooks). Útil para filtrar el tráfico en tus logs.
  • X-CaptainDNS-Event-ID: identificador estable del evento de negocio. Se mantiene idéntico en todos los intentos y en los reenvíos manuales. Clave recomendada para deduplicar en el lado del receptor.
  • X-CaptainDNS-Delivery-ID: identificador único del intento. Cambia en cada reintento y en cada reenvío.
  • X-CaptainDNS-Attempt: contador con formato n/6 (número máximo de intentos).
  • X-CaptainDNS-Event-Type: copia del campo event_type del body, práctico para enrutar sin parsear el JSON.
  • X-CaptainDNS-Signature: presente si hay un secreto configurado. Formato sha256=<hex>.
  • X-CaptainDNS-Timestamp: presente si hay un secreto configurado. Timestamp Unix en segundos, usado en el cálculo de la firma y para protegerse contra replays.
  • Timeout de dispatch: CaptainDNS espera como máximo 10 segundos una respuesta de tu endpoint. Superado ese tiempo, el intento se considera fallido.
  • Reintentos: 6 intentos en total (1 inicial + 5 reintentos), backoff 10s, 1min, 10min, 1h, 6h, 24h para los estados 5xx, 408, 429 y los errores de red (timeout, DNS, TLS). Los demás 4xx (400, 401, 403, 404, 422, etc.) pasan inmediatamente a failed_permanent sin reintento. Tras 20 entregas failed_permanent consecutivas, el canal se desactiva automáticamente y se envía un email al owner.

Formato del payload

El cuerpo JSON sigue el esquema V2 (schema_version: "2"):

{
  "schema_version": "2",
  "event_id": "7a3c9b1e-2f4d-4a6b-8c7d-9e1f2a3b4c5d",
  "delivery_id": "b8c1f4e2-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
  "attempt": 1,
  "event_type": "MONITOR_DOWN",
  "category": "monitoring",
  "timestamp": "2026-04-14T10:30:45Z",
  "subject": "[CaptainDNS] Monitor DOWN",
  "status": "sent",
  "data": {
    "monitor_id": "mon_3f8a1c",
    "target": "captaindns.com",
    "failure_reason": "connection timeout"
  }
}
  • schema_version: versión del esquema de payload. Vale "2" para todas las entregas actuales.
  • event_id: UUID estable en todos los intentos y reenvíos del mismo evento. Clave de deduplicación recomendada.
  • delivery_id: UUID único por intento. Útil para correlacionar con el dashboard Deliveries.
  • attempt: número del intento (1 a 6).
  • event_type: identificador estable del evento, en SCREAMING_SNAKE_CASE. Consulta la lista a continuación.
  • category: categoría webhook entre monitoring, deployment, dns.
  • timestamp: fecha de emisión en formato RFC 3339 UTC.
  • subject: etiqueta legible, reutilizada como asunto de email para el canal mail equivalente.
  • status: estado del dispatch en CaptainDNS, normalmente "sent".
  • data: objeto libre cuya forma depende del event_type. Puede omitirse o estar vacío según el evento.

Trata el payload como tolerante: pueden aparecer nuevos campos sin un bump de versión. Tu parser debe ignorarlos silenciosamente.

Verificación de la firma

Cuando hay un secreto configurado, CaptainDNS firma la petición con HMAC-SHA256 para que el receptor pueda verificar el origen y la integridad del payload.

Algoritmo:

  1. Recupera el timestamp del header X-CaptainDNS-Timestamp y el cuerpo crudo de la petición (antes de cualquier parsing JSON).
  2. Concatena <timestamp>.<body_crudo>.
  3. Calcula el HMAC-SHA256 con tu secreto.
  4. Compara el resultado hex con el contenido del header X-CaptainDNS-Signature (después de quitar el prefijo sha256=), en tiempo constante.

Rechaza cualquier petición cuyo timestamp se desvíe más de 5 minutos del tiempo actual, para limitar los ataques por replay.

Node.js

import crypto from "node:crypto";

function verifyWebhook(rawBody, signatureHeader, timestamp, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.`)
    .update(rawBody)
    .digest("hex");
  const received = signatureHeader.replace(/^sha256=/, "");
  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(received, "hex"),
  );
}

Importante: rawBody debe ser el buffer crudo tal cual se recibe, antes de JSON.parse. Con Express, usa express.raw({ type: "application/json" }) en la ruta del webhook. Con Next.js, lee el stream con await request.text().

Python

import hmac, hashlib

def verify_webhook(raw_body: bytes, signature_header: str, timestamp: str, secret: str) -> bool:
    mac = hmac.new(secret.encode(), digestmod=hashlib.sha256)
    mac.update(f"{timestamp}.".encode())
    mac.update(raw_body)
    expected = mac.hexdigest()
    received = signature_header.removeprefix("sha256=")
    return hmac.compare_digest(expected, received)

Con FastAPI, recupera el cuerpo crudo con await request.body(). Con Flask, usa request.get_data() sin acceder previamente a request.json.

Lista de eventos

Los 22 eventos emitidos actualmente, agrupados por categoría webhook (monitoring, deployment, dns). Cada evento pertenece a una única categoría, usada para el filtrado en el servidor según las categorías marcadas en el canal.

Categoría monitoring (7)

Uptime, salud TLS, redirecciones caídas, alertas transversales.

event_typeDescripción
MONITOR_DOWNUn monitor pasa al estado DOWN tras varios fallos consecutivos.
MONITOR_RECOVERYUn monitor vuelve a UP tras un DOWN.
MONITOR_DISABLE_WARNINGUn monitor en fallo permanente se acerca a la desactivación automática.
MONITOR_AUTO_DISABLEDUn monitor se ha desactivado automáticamente tras demasiados fallos prolongados.
TLS_EXPIRY_WARNINGUn certificado TLS observado se acerca a su fecha de expiración.
REDIRECT_DOWNUna redirección hospedada ya no responde correctamente.
GENERIC_ALERTAlerta transversal que no encaja en ninguna categoría dedicada.

Categoría deployment (13)

Activación, fallos y desactivación de las políticas mail hospedadas (MTA-STS, TLS-RPT, DMARC, BIMI), redirecciones, verificación de dominio.

event_typeDescripción
MTASTS_ACTIVATEDUna política MTA-STS hospedada ha pasado a modo enforce.
MTASTS_DEPLOY_FAILUREUn despliegue MTA-STS ha fallado (DNS, certificado o policy).
MTASTS_DEACTIVATEDUna política MTA-STS hospedada se ha desactivado.
TLSRPT_ACTIVATEDUn registro TLS-RPT hospedado se ha activado.
TLSRPT_DEPLOY_FAILUREUn despliegue TLS-RPT ha fallado.
TLSRPT_HIGH_FAILUREUn informe TLS-RPT agregado señala una tasa de fallos elevada.
DMARC_ACTIVATEDUna política DMARC hospedada ha pasado a quarantine o reject.
DMARC_DEPLOY_FAILUREUn despliegue DMARC ha fallado.
DMARC_ALIGNMENT_DROPLa tasa de alineación DMARC cae por debajo de un umbral crítico.
BIMI_CERT_EXPIRYUn certificado VMC o CMC hospedado se acerca a su expiración.
REDIRECT_ACTIVATEDUna redirección hospedada se ha activado.
REDIRECT_DEACTIVATEDUna redirección hospedada se ha desactivado.
DOMAIN_REVERIFY_FAILEDUna reverificación periódica de propiedad de dominio ha fallado.

Categoría dns (2)

Diffs DNS y anomalías de latencia detectadas por las resolve watches.

event_typeDescripción
RESOLVE_WATCH_DIFFUna resolve watch detecta un cambio en la respuesta DNS.
RESOLVE_LATENCY_ANOMALYUna resolve watch detecta una anomalía de latencia significativa.

Panel de entregas

Cada intento se persiste en la tabla webhook_deliveries y se consulta en el dashboard, subpestaña Deliveries de la sección Notificaciones. Columnas disponibles: timestamp, canal, event_type, código HTTP devuelto, contador attempts sobre 6, estado (pending, retrying, sent, failed_permanent).

Las entregas failed_permanent (6 intentos agotados) pueden reenviarse manualmente desde la tabla. Un reenvío inserta una nueva fila con el mismo event_id pero un nuevo delivery_id y reinicia el contador a 0: se planifican así hasta 6 nuevos intentos.

El historial se conserva durante 90 días, sin distinción de plan, y luego lo depura un worker diario. Una versión futura alineará la retención con la duración de conservación de los logs del plan (log_retention_days).

Un botón Send test en cada canal webhook activo envía un payload ficticio event_type: "TEST" en la categoría monitoring. Limitado a 5 tests por minuto y por perfil.

Buenas prácticas en el lado del receptor

  • Responde rápido: devuelve un estado 2xx en menos de 5 segundos. Si el procesamiento es largo, confirma inmediatamente y delega a una cola asíncrona.
  • Verifica la firma antes de confiar: nunca deserialices el payload sin haber validado primero X-CaptainDNS-Signature, salvo para leer el timestamp.
  • Tolera campos desconocidos: tu parser debe ignorar silenciosamente las claves no conocidas, tanto en la raíz como dentro de data. Esto garantiza la compatibilidad ascendente.
  • Trata event_type como una enumeración abierta: ignora los tipos que no sepas gestionar en lugar de fallar.
  • Idempotencia aplicativa: la misma notificación puede llegar varias veces (reintento de red, reenvío manual desde el dashboard). Usa event_id como clave de deduplicación: permanece estable en todos los intentos y en todos los reenvíos de un mismo evento. delivery_id cambia en cada intento.
  • Loguea event_id y delivery_id: útil para correlacionar con la subpestaña Deliveries del dashboard durante un incidente.

Limitaciones actuales y evoluciones

  • Filtrado por categoría, no por event_type: los canales se suscriben a monitoring, deployment o dns. Si solo quieres procesar un subconjunto concreto (por ejemplo solo MONITOR_DOWN), filtra en el receptor por el campo event_type.
  • Endpoints CRUD dashboard-only: las rutas /v1/notifications/channels/* y /v1/notifications/deliveries/* están protegidas por la sesión de Auth0 y no son accesibles con una clave API pública cdns_live_* / cdns_test_*. La gestión programática de los canales vía clave API está en el roadmap.
  • Retención fija de 90 días: la alineación con log_retention_days (plan) está prevista para una iteración posterior.
  • Firmas HMAC-SHA256 únicamente: el soporte de Ed25519 y la rotación de secreto con grace period se están estudiando para una versión futura.

Las novedades se anunciarán en el changelog de la API pública.