docs · para desarrolladores

Webhooks

Notificaciones push para lo que importa a tus sistemas: un escaneo terminado, un nuevo rastreador, una versión publicada, una exportación de prueba completada. Firmadas, reintentadas e inspeccionables.

Endpoints

Crea endpoints en Configuración del espacio de trabajo → Desarrolladores → Webhooks o a través de la API. Solo se aceptan URL https en hosts públicos; las direcciones privadas e internas se rechazan al registrarlas. El secreto de firma (whsec_…) se muestra una vez al crearlo y puede rotarse en cualquier momento; tras una rotación, verifica con ambos secretos hasta que tu despliegue se haya puesto al día.

Eventos

Doce tipos de eventos, suscribibles individualmente o con *:

scan.completed · scan.failed · tracker.new · issue.opened · issue.resolved · alert.raised · config.published · version.promoted · export.completed · declaration.updated · install.broken · plan.changed

Verificar una entrega

Las entregas siguen la convención Standard Webhooks. Tres cabeceras llegan con cada POST:

webhook-id:        <delivery id>
webhook-timestamp: <unix seconds>
webhook-signature: v1,<base64(hmac_sha256(key, id.timestamp.body))>

La clave HMAC es la parte base64 de tu secreto whsec_, decodificada a bytes en bruto. Recalcula la firma sobre id + "." + timestamp + "." + body, compara en tiempo constante y rechaza cualquier cosa con más de cinco minutos:

import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(secret, headers, rawBody) {
  const key = Buffer.from(secret.slice('whsec_'.length), 'base64');
  const msg = headers['webhook-id'] + '.' + headers['webhook-timestamp'] + '.' + rawBody;
  const want = createHmac('sha256', key).update(msg).digest();
  const got = Buffer.from(headers['webhook-signature'].split(',')[1], 'base64');
  const fresh = Math.abs(Date.now() / 1000 - Number(headers['webhook-timestamp'])) < 300;
  return fresh && want.length === got.length && timingSafeEqual(want, got);
}

Reintentos y reenvío

Una entrega fallida se reintenta con backoff: 1 minuto, luego 5 minutos, 30 minutos, 2 horas, 6 horas; después se marca como muerta. Cada intento, código de respuesta y siguiente reintento es visible en el registro de entregas del endpoint, y cualquier entrega puede reenviarse a mano; un reenvío hace referencia al original. El historial de entregas se conserva 90 días.

Responder

Responde rápido con cualquier 2xx y haz el trabajo real de forma asíncrona; cualquier otra cosa cuenta como fallo y programa un reintento. Las entregas pueden llegar desordenadas y, raramente, más de una vez; usa webhook-id para la idempotencia.