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.
Crear un punto de entrega
Sección titulada «Crear un punto de entrega»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.
Cuándo sale una entrega
Sección titulada «Cuándo sale una entrega»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.
La petición
Sección titulada «La petición»Un POST por evento, con cuerpo JSON:
POST <tu URL>content-type: application/jsonuser-agent: perstat-webhook/1.0x-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.
Verificar la firma
Sección titulada «Verificar la firma»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 hashlibimport 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.
Reglas de entrega
Sección titulada «Reglas de entrega»- Una respuesta
2xxcuenta como entregada. Cualquier otro estado, incluido un3xx, 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.
Siguientes pasos
Sección titulada «Siguientes pasos»- Alertas y guardias: coloca el punto de entrega en una cadena de escalado.
- API: gestiona webhooks y lee incidentes con una clave de API.