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 |
| Solo | 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 29 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 (9)
Uptime, salud TLS, redirecciones caídas, monitorización de entregabilidad, 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. |
DELIVERABILITY_CHANGE | La configuración de un dominio vigilado ha cambiado: una puntuación de pilar o un registro. |
DELIVERABILITY_DOMAIN_UNRESOLVABLE | Un dominio vigilado ya no resuelve en absoluto a nivel DNS. |
Categoría deployment (18)
Activación, fallos y desactivación de las políticas mail hospedadas (MTA-STS, TLS-RPT, DMARC, BIMI), campaña de republicación MTA-STS, pérdida de correo legítimo, 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. |
MTASTS_SERIAL_ROTATED | El serial de una política MTA-STS hospedada ha rotado: falta republicar el registro TXT. |
MTASTS_REPUBLISH_REMINDER | Recordatorio para republicar el TXT MTA-STS, 24 horas después de la rotación del serial. |
MTASTS_REPUBLISH_FINAL | Último aviso antes de desactivar la política MTA-STS, 48 horas después de la rotación. |
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. |
TLSRPT_LOSING_MAIL | Los informes TLS-RPT muestran sesiones bloqueadas: el dominio pierde correo legítimo. |
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. |
DMARC_LOSING_MAIL | Los informes DMARC muestran correo del propio dominio que falla la alineación. |
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.
Códigos de error
Los errores devueltos por los endpoints CRUD utilizan los siguientes códigos:
| Estado | Código | Significado |
|---|---|---|
400 | invalid_categories | La lista de categorías enviada está vacía o contiene valores no admitidos. |
402 | webhook_quota_exceeded | El perfil ha alcanzado el límite de canales activos de su plan. |
422 | channel_not_webhook | El canal indicado no es un webhook (no se puede enviar un test). |
422 | channel_disabled | El canal está desactivado (no se puede enviar un test). |
429 | test_rate_limited | Más de 5 tests por minuto y por perfil. |
También puede aparecer un 409 al crear un canal cuando la capa de almacenamiento rechaza la inserción (límite de canales alcanzado). Devuelve un mensaje en texto plano, no un código de máquina.
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.