Ir al contenido

Webhook firmado

El webhook firmado conecta Perstat con destinos propios: una herramienta de incidentes que acepta fuentes de alerta genéricas, una automatización que operas tú mismo o un script. Los destinos de chat, PagerDuty y Opsgenie tienen conectores propios, hechos a su medida.

En la aplicación, en Integraciones, crea un conector del tipo Webhook y asígnale una URL (propietario y administrador). Perstat genera un secreto de firma y lo muestra exactamente una vez. Después aparece enmascarado, y puedes rotarlo cuando quieras. La rotación sustituye el secreto de inmediato y el anterior deja de verificar al instante, así que actualiza tu receptor en el mismo paso.

Con webhooks:write, la API REST crea, modifica, prueba y borra webhooks y rota sus secretos, así que tus scripts de aprovisionamiento también pueden gestionarlos.

El secreto tiene la forma whsec_ seguido de 64 caracteres hexadecimales en minúsculas. Trata la cadena completa como una clave opaca.

Las URL http:// se aceptan para destinos locales. Usa https:// para todo lo que atraviese una red que no sea tuya. El destino debe ser alcanzable públicamente: un host que resuelva a una dirección privada, de bucle local o de enlace local se rechaza en cada envío.

Lo que recibe un punto de entrega depende de si tu organización tiene una cadena de escalado activa: un plan con rotación de guardias (a partir de Sentinel, mira planes y límites), la guardia activada y la cadena habilitada.

Sin cadena activa (en cualquier otro plan, o con la guardia o la cadena desactivadas), cada punto de entrega activo recibe los eventos a los que está suscrito en Integraciones:

Evento Cuándo
incident.opened Se abre un incidente, crítico o degradado. Una alerta retenida por una regla de dependencia se entrega en cuanto se libera.
incident.resolved El incidente se cierra.
incident.test Pulsas Probar en el conector. Se entrega de forma síncrona, solo a este punto de entrega.

Con una cadena activa, la cadena dirige los mensajes de incidente salientes. Un punto de entrega recibe los mensajes de los incidentes críticos desde los pasos que lo nombran como destinatario, en el minuto 0 o después:

Evento Cuándo
incident.escalation Se dispara un paso que nombra el punto de entrega. Es también el primer mensaje para un paso en el minuto 0: el evento dice “este incidente está sin confirmar”, no “acaba de abrirse uno nuevo”.
incident.resolved El incidente se cierra. Va a cada punto de entrega que un paso alertó, suscrito o no, y a cada punto de entrega activo suscrito a él. Lo que la cadena abre, la cadena lo cierra.
incident.test Como arriba.

Con una cadena activa, la suscripción a incident.opened no tiene efecto. Un paso omite un punto de entrega desactivado, y omite todos los puntos de entrega mientras el incidente está confirmado, mientras su monitor está desactivado o archivado y mientras el monitor se encuentra en una ventana de mantenimiento. Un punto de entrega desactivado durante el incidente recibe igualmente el fin de alerta de un incidente sobre el que fue alertado.

Un POST por evento, con cuerpo JSON:

POST <tu URL>
content-type: application/json
user-agent: perstat-webhook/1.0
x-perstat-signature: sha256=<64 caracteres hexadecimales en minúsculas>
{
"event": "incident.escalation",
"incident": {
"id": "inc_9f3c2a7e1b4d48c0a6e5f1d2c3b4a596",
"severity": "critical",
"cause": "HTTP 503",
"summary": "La API no responde",
"monitor": { "id": "mon_4b1e8d2c7a9f4e6b8c0d1e2f3a4b5c6d", "name": "API" },
"started_at_unix": 1757930400,
"resolved_at_unix": null
},
"organization": { "id": "org_2d7a9c4e1f3b4a5c8d6e7f8a9b0c1d2e", "name": "Acme" }
}
Campo Significado
event Uno de los eventos anteriores.
incident.id Estable en todos los eventos de un mismo incidente. Úsalo para abrir y cerrar el mismo caso en tu lado. PagerDuty lo recibe como dedup_key, Opsgenie como alias.
incident.severity critical o degraded. A través de una cadena activa recibes critical. La prueba también envía critical.
incident.cause Texto libre: el detalle del primer check fallido, o la causa introducida en un incidente manual. test en una entrega de prueba.
incident.summary Opcional, null si está vacío.
incident.monitor id y name del monitor vinculado. null en un incidente manual sin monitor y en la prueba.
incident.started_at_unix Segundos Unix.
incident.resolved_at_unix Segundos Unix, null hasta que el incidente se resuelve.
organization id y name de la organización a la que pertenece el punto de entrega.

El cuerpo es estable y crece de forma aditiva: lee lo que necesites e ignora los campos que no conozcas.

La cabecera lleva sha256= seguido del HMAC-SHA256 del cuerpo bruto de la petición, con la cadena del secreto como clave, exactamente como se te mostró: sus bytes ASCII son la clave, no se decodifica. Calcula el MAC sobre los bytes recibidos, antes de cualquier análisis JSON, y compara en tiempo constante.

Node:

import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, rawBody, header) {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
return expected.length === header.length
&& timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}

Python:

import hashlib
import hmac
def verify(secret: str, raw_body: bytes, header: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header)

Un cuerpo que no supera la verificación no vino de Perstat, o fue alterado por el camino. Responde 401 y no hagas nada más.

La firma cubre solo el cuerpo, sin marca de tiempo ni identificador de entrega, así que una petición capturada vuelve a superar la verificación si se reproduce. Entrega sobre TLS y trata un par (incident.id, event) repetido como ya atendido: para una herramienta de incidentes, cerrar dos veces una alerta es inofensivo, abrirla dos veces es ruido.

  • Una respuesta 2xx cuenta como entregada. Cualquier otro estado, incluido un 3xx, cuenta como fallido, y no se siguen redirecciones.
  • El tiempo de espera es de 10 segundos por entrega. Responde rápido y haz el trabajo después.
  • Perstat entrega cada evento una vez.
  • El resultado de la última entrega, con el texto de error si lo hubo, se muestra en el conector en Integraciones y junto al paso en la cadena.

Mantén el receptor disponible y concilia los eventos que falten mediante la API REST: lee un incidente por su incident.id, o lista con incidents:read los incidentes abiertos de la organización.

  • Alertas y guardias: coloca el punto de entrega en una cadena de escalado.
  • API: gestiona webhooks y lee incidentes con una clave de API.