Webhook firmado
El webhook firmado es el conector para destinos propios: una herramienta de incidentes que acepta fuentes de alerta genéricas, una automatización que opera usted mismo o un script. Los destinos de chat, PagerDuty y Opsgenie tienen conectores propios; esta página trata solo del tipo genérico. Todo lo que sigue describe lo que se entrega hoy.
Crear un punto de entrega
Sección titulada «Crear un punto de entrega»En la aplicación, en Integraciones, cree un conector del tipo Webhook y asígnele una URL (solo propietario y administrador). Perstat genera un secreto de firma y lo muestra exactamente una vez; después solo aparece enmascarado y únicamente puede rotarse. La rotación sustituye el secreto de inmediato. No hay periodo de solapamiento en el que ambos sean válidos.
El secreto tiene la forma whsec_ seguido de 64 caracteres hexadecimales en minúsculas.
Trate la cadena completa como una clave opaca.
Las URL http:// se aceptan para destinos locales; use https:// para todo lo que
atraviese una red que no sea suya. 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 su organización tiene una cadena de escalado activa: un plan con rotación de guardias (a partir de Sentinel, vea 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 |
Pulsa 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 se entera de una caída solo si un paso lo nombra como destinatario, en el minuto 0 o después, y solo para incidentes críticos:
| 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 <su 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. Úselo para abrir y cerrar el mismo caso de su lado; PagerDuty lo recibe como dedup_key, Opsgenie como alias. |
incident.severity |
critical o degraded. A través de una cadena activa solo verá critical; la prueba también envía critical. |
incident.cause |
Texto libre: el detalle de la primera comprobación fallida, 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 solo se amplía de forma aditiva: lea lo que necesite e ignore los campos que no conozca.
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 le mostró: sus bytes ASCII son
la clave, no se decodifica. Calcule el MAC sobre los bytes recibidos, antes de cualquier
análisis JSON, y compare 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.
Responda 401 y no haga nada más.
La firma no lleva marca de tiempo ni identificador de entrega. Una petición capturada
puede por tanto reproducirse y volverá a superar la verificación. Entregue sobre TLS y
trate 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 otra, incluida una3xx, cuenta como fallida; no se siguen redirecciones. - El tiempo de espera es de 10 segundos por entrega. Responda rápido y haga el trabajo después.
- Un intento por evento. No hay cola de reintentos; una entrega fallida sigue fallida.
- 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.
Como no hay reintento, un receptor brevemente caído pierde ese evento. Si necesita el
cuadro completo en lugar del último mensaje, lea el incidente a través de la
API usando incident.id.