Un webhook es una dirección de su sistema a la que Hermetry llama cuando ocurre un evento. Úselo para alimentar un sistema de tickets, un ERP, un bus de mensajes o su propio servicio. Los webhooks los gestionan los administradores en Webhooks (en la parte Developer del menú). El envío de webhooks requiere un plan que los incluya.
Crear un webhook
- Abra Webhooks y seleccione New Webhook.
- Introduzca un Name y, opcionalmente, una Description («¿Para qué sirve este webhook?»).
- Introduzca la URL. Debe ser HTTPS: «Webhook URL must be a valid HTTPS URL.»
- En Events, seleccione al menos un evento. La lista muestra solo los eventos de los módulos que usa su organización.
- Opcionalmente:
- Custom headers (optional): hasta 20 cabeceras enviadas en cada llamada, por ejemplo un token de autorización que su receptor comprueba.
Content-Type,User-Agent,Host,Content-LengthyX-Webhook-*no se pueden definir. Los valores ocupan una sola línea y como máximo 1.000 caracteres. - Event filters (optional, advanced): entrega un evento solo cuando su carga útil coincide (véase más abajo).
- Tags (optional, comma-separated): etiquetas para organizar sus webhooks, hasta 20.
- Rate limit (optional, deliveries/minute): vacío significa sin límite.
- Custom headers (optional): hasta 20 cabeceras enviadas en cada llamada, por ejemplo un token de autorización que su receptor comprueba.
- Guarde. Copie el Signing secret ahora: solo se muestra una vez.
Todos los eventos de los módulos IoT se enumeran con cargas útiles de ejemplo en la Webhook event reference.
Filtros de eventos
Un filtro de evento es un mapa JSON que va de un evento a los campos de la carga útil que debe cumplir. Una entrega se omite cuando la carga útil no coincide. Los eventos sin filtro se entregan siempre.
{
"alarm.created": { "severity": "CRITICAL" },
"device.disconnected": { "profileId": "6f1c0a2e-0000-4000-8000-000000000001" }
}
Cada ruta nombra un campo de data del evento y puede usar puntos, por ejemplo originator.type. Cada campo indicado debe ser igual a su valor (números y texto se comparan como texto, así que "2" coincide con 2). Como máximo 50 eventos pueden tener filtro.
Aspecto de una entrega
Hermetry envía un POST con estas cabeceras:
| Cabecera | Valor |
|---|---|
Content-Type | application/json (formato por defecto) o application/cloudevents+json; charset=utf-8 |
User-Agent | Hermetry-Webhooks/1.0 |
X-Webhook-Event | El nombre del evento, por ejemplo device.disconnected |
X-Webhook-Delivery | El id de la entrega. Es el mismo en cada reintento y reenvío de una entrega. |
X-Webhook-Timestamp | Segundos Unix en que se firmó este intento |
X-Webhook-Signature-V2 | sha256= + HMAC-SHA256 de "<timestamp>.<body>" con su secreto. Use esta. |
X-Webhook-Signature | HMAC-SHA256 solo del cuerpo. Forma antigua, mantenida durante un periodo de transición. |
X-Webhook-Signature-Prev, X-Webhook-Signature-V2-Prev | Lo mismo con el secreto anterior, durante una rotación de secreto |
X-Webhook-Manual | true en un evento que un administrador envió manualmente |
X-Idempotency-Key | En los mensajes que un flujo envía con su bloque de webhook |
Con el formato por defecto, el cuerpo es un sobre:
{
"webhookId": "…",
"tenantId": "…",
"event": "device.disconnected",
"createdAt": "2026-11-03T12:00:30.000Z",
"data": { "deviceId": "…", "name": "router-17", "reason": "timeout", "…": "…" }
}
Un evento que un administrador envió manualmente también tiene "manual": true. Responda con cualquier estado 2xx dentro del tiempo de espera. Los primeros 4 KB del cuerpo de su respuesta se conservan en el registro de entregas.
Formato CloudEvents
Un endpoint puede recibir CloudEvents 1.0 en modo estructurado. El formulario del webhook aún no ofrece elegir formato: establezca "format": "cloudevents" mediante la API (POST o PATCH /api/webhooks/..., véase más abajo). "json" es el valor por defecto.
{
"specversion": "1.0",
"id": "8d2c…",
"source": "urn:hermetry:tenant:<tenantId>",
"type": "com.hermetry.device.connected",
"datacontenttype": "application/json",
"time": "2026-11-03T10:00:30.000Z",
"data": { "deviceId": "…", "name": "router-17", "transport": "mqtts", "…": "…" },
"tenantid": "<tenantId>",
"webhookid": "<webhookId>"
}
data contiene exactamente lo que el formato por defecto contiene bajo data. id es el mismo en cada reintento y reenvío, así que deduplique por source e id. Las cabeceras de firma son las mismas en ambos formatos. Los modos por lotes y binario no se ofrecen.
Verificar la firma
Verifique contra el cuerpo sin procesar, antes de analizarlo, y rechace marcas de tiempo antiguas.
import crypto from 'crypto';
function verifyWebhook(body: string, timestamp: string, signatureV2: string, secret: string): boolean {
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(`${timestamp}.${body}`).digest('hex');
// Reject deliveries older than 5 minutes.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const a = Buffer.from(expected);
const b = Buffer.from(signatureV2);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Durante una rotación de secreto, acepte una llamada cuando coincida X-Webhook-Signature-V2 o X-Webhook-Signature-V2-Prev.
Entrega, reintentos y mensajes fallidos definitivos
| Ajuste | Por defecto | Rango |
|---|---|---|
| Request Timeout (ms) | 15,000 | 1.000 a 120.000 |
| Max Delivery Attempts (incluido el primero) | 3 | 1 a 10 |
| Retry Backoff (ms): la espera antes del reintento 1, 2, … | 60000,300000,900000 | 1 a 9 valores, cada uno de 1.000 a 86.400.000; más allá de la lista, se usa el último valor |
| Circuit Breaker Threshold: fallos consecutivos antes de desactivar el endpoint | 10 | 1 a 100.000 |
| Default Rate Limit (per minute): para los endpoints sin límite propio | sin límite | vacío o 0 = sin límite |
Establézcalos en Webhooks > ajustes (el botón de engranaje). Una entrega está en uno de estos estados: PENDING, SUCCESS, FAILED (se reintentará) o DEAD_LETTERED (se agotaron todos los intentos).
- Cada entrega se registra con su estado, intentos, código de respuesta, error, duración y hora del siguiente reintento.
- Un endpoint que supera su límite de tasa recibe sus entregas unos 60 segundos más tarde; no se descarta nada.
- Tras el umbral de fallos consecutivos, el endpoint se desactiva y aparece como desactivado automáticamente. Los reintentos en cola se detienen. Vuelva a activarlo para reiniciar el contador y luego reenvíe o repita lo que falló.
- Las entregas son al menos una vez. Deduplique por
X-Webhook-Delivery(o por elidde CloudEvents). - No se garantiza que los eventos lleguen en orden. Use las horas indicadas en la carga útil.
Los eventos de conexión y de cambio de dispositivo tienen su propio control de volumen: por defecto, como máximo 600 de cada tipo por organización y por minuto. Véase la Webhook event reference.
Rotar el secreto
Seleccione Rotate signing secret (rol Owner). El nuevo secreto se muestra una vez. El secreto anterior sigue válido durante 48 horas, y en ese tiempo cada llamada lleva ambas firmas. Rotar de nuevo mientras esa ventana está abierta reemplaza solo el secreto actual; el anterior conserva su fecha de fin.
Prueba
Desde las acciones de fila de un webhook o desde su página:
- Send test event envía de inmediato un evento llamado
testy muestra el resultado («Test delivered — HTTP 200 in 85 ms»). Una prueba nunca cuenta para el circuit breaker. - Trigger event… envía un evento real y firmado que usted elige, con una carga útil de ejemplo (objeto JSON, como máximo 64 KB), marcado con
manual: trueyX-Webhook-Manual: true. Respeta el filtro de eventos del endpoint: una muestra que el filtro descartaría se rechaza. - View deliveries lista las entregas; una entrega fallida o en mensajes fallidos definitivos puede reenviarse, y todos los mensajes fallidos definitivos pueden repetirse a la vez.
Reglas de red
Las direcciones de webhook deben ser direcciones HTTPS públicas. Las direcciones privadas, de bucle local, link-local y de metadatos de nube se rechazan al guardar y de nuevo en cada llamada, y no se siguen las redirecciones. Para un destino interno intencionado, un administrador puede definir un ipAllowlist explícito (hasta 50 direcciones o rangos CIDR) en el webhook mediante la API.
La API de webhooks
Las rutas están bajo /tenant/<tenantId>. Todas requieren los permisos de un administrador.
| Método | Ruta | Función |
|---|---|---|
| GET | /api/webhooks/events | Los eventos a los que puede suscribirse |
| GET, POST | /api/webhooks | Lista (page desde 1, pageSize, search, sort, isActive, autoDisabled); crear: 201 { webhook, secret } |
| GET, PATCH, DELETE | /api/webhooks/{webhookId} | Leer, modificar, eliminar |
| POST | /api/webhooks/{webhookId}/rotate-secret | { webhook, newSecret, overlapMs } (Owner) |
| POST | /api/webhooks/{webhookId}/test | { delivery } |
| POST | /api/webhooks/{webhookId}/trigger | { event, payload } → { message, deliveryId } |
| GET | /api/webhooks/{webhookId}/deliveries | { data, total }; filtros status, event, createdFrom, createdTo |
| POST | /api/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver | Volver a encolar una entrega FAILED o DEAD_LETTERED |
| GET, POST | /api/webhooks/{webhookId}/deliveries/replay-dead-letter | Contar, o volver a encolar, cada dead letter |
| GET | /api/webhooks/metrics, /api/webhooks/report | Estadísticas de entrega (?webhookId=&days=) |
Cuerpo de creación: name, description, url, events, headers, eventFilters, tags, rateLimitPerMinute (de 1 a 100.000), ipAllowlist, format (json o cloudevents).
| Rechazo | Significado |
|---|---|
403 WEBHOOKS_NOT_IN_PLAN | Su plan no incluye webhooks |
409 ENDPOINT_DISABLED | El endpoint está desactivado; actívelo primero |
409 DELIVERY_NOT_REDELIVERABLE | La entrega está pendiente o ya se ha realizado con éxito |
422 EVENT_FILTERED_OUT | El filtro del endpoint descartaría la muestra que ha disparado |
422 | Un evento que ningún módulo de su organización ofrece |

