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:
| Plan | Canales activos |
|---|---|
| Free | 1 |
| Starter | 3 |
| Pro | 10 |
| Business | 25 |
| Enterprise | 100 |
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:
POSTsobre la URL exacta configurada. Content-Type: siempreapplication/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 formaton/6(número máximo de intentos).X-CaptainDNS-Event-Type: copia del campoevent_typedel body, práctico para enrutar sin parsear el JSON.X-CaptainDNS-Signature: presente si hay un secreto configurado. Formatosha256=<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, 24hpara los estados5xx,408,429y los errores de red (timeout, DNS, TLS). Los demás4xx(400, 401, 403, 404, 422, etc.) pasan inmediatamente afailed_permanentsin reintento. Tras 20 entregasfailed_permanentconsecutivas, 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 entremonitoring,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 delevent_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:
- Recupera el timestamp del header
X-CaptainDNS-Timestampy el cuerpo crudo de la petición (antes de cualquier parsing JSON). - Concatena
<timestamp>.<body_crudo>. - Calcula el HMAC-SHA256 con tu secreto.
- Compara el resultado hex con el contenido del header
X-CaptainDNS-Signature(después de quitar el prefijosha256=), 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_type | Descripción |
|---|---|
MONITOR_DOWN | Un monitor pasa al estado DOWN tras varios fallos consecutivos. |
MONITOR_RECOVERY | Un monitor vuelve a UP tras un DOWN. |
MONITOR_DISABLE_WARNING | Un monitor en fallo permanente se acerca a la desactivación automática. |
MONITOR_AUTO_DISABLED | Un monitor se ha desactivado automáticamente tras demasiados fallos prolongados. |
TLS_EXPIRY_WARNING | Un certificado TLS observado se acerca a su fecha de expiración. |
REDIRECT_DOWN | Una redirección hospedada ya no responde correctamente. |
GENERIC_ALERT | Alerta 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_type | Descripción |
|---|---|
MTASTS_ACTIVATED | Una política MTA-STS hospedada ha pasado a modo enforce. |
MTASTS_DEPLOY_FAILURE | Un despliegue MTA-STS ha fallado (DNS, certificado o policy). |
MTASTS_DEACTIVATED | Una política MTA-STS hospedada se ha desactivado. |
TLSRPT_ACTIVATED | Un registro TLS-RPT hospedado se ha activado. |
TLSRPT_DEPLOY_FAILURE | Un despliegue TLS-RPT ha fallado. |
TLSRPT_HIGH_FAILURE | Un informe TLS-RPT agregado señala una tasa de fallos elevada. |
DMARC_ACTIVATED | Una política DMARC hospedada ha pasado a quarantine o reject. |
DMARC_DEPLOY_FAILURE | Un despliegue DMARC ha fallado. |
DMARC_ALIGNMENT_DROP | La tasa de alineación DMARC cae por debajo de un umbral crítico. |
BIMI_CERT_EXPIRY | Un certificado VMC o CMC hospedado se acerca a su expiración. |
REDIRECT_ACTIVATED | Una redirección hospedada se ha activado. |
REDIRECT_DEACTIVATED | Una redirección hospedada se ha desactivado. |
DOMAIN_REVERIFY_FAILED | Una 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_type | Descripción |
|---|---|
RESOLVE_WATCH_DIFF | Una resolve watch detecta un cambio en la respuesta DNS. |
RESOLVE_LATENCY_ANOMALY | Una 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
2xxen 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_typecomo 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_idcomo clave de deduplicación: permanece estable en todos los intentos y en todos los reenvíos de un mismo evento.delivery_idcambia en cada intento. - Loguea
event_idydelivery_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 amonitoring,deploymentodns. Si solo quieres procesar un subconjunto concreto (por ejemplo soloMONITOR_DOWN), filtra en el receptor por el campoevent_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úblicacdns_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.