Saltar al contenido

Guías

Webhooks

Un endpoint de webhook recibe lo que las reglas de alerta de un espacio de trabajo le envían: una release bloqueada, un resultado que espera a una persona, una ejecución gestionada que terminó, la alarma de un monitor de producción. Cada envío es un cuerpo JSON firmado con el secreto del endpoint, para que su servidor pueda comprobar que procede de su espacio de trabajo antes de actuar.

Añadir un endpoint

En la página Integraciones del espacio de trabajo, un Owner o un Admin añade un webhook firmado con un nombre y una URL https pública en el puerto 443 u 8443. La página muestra entonces el secreto de firma del endpoint, que empieza por whsec_. Cópielo en ese momento: se guarda cifrado y no se vuelve a mostrar. Para reemplazarlo, elimine el endpoint y añádalo de nuevo.

Después, en la página Alertas, elija el endpoint como canal de una o varias reglas. «Enviar una prueba» publica un envío oloproof.test y muestra el estado con que respondió su servidor.

Lo que se envía

{
  "type": "oloproof.gate_blocked",
  "id": "dlv_8c1f0a2b3c4d5e6f7a8b9c0d",
  "created_at": "2026-10-26T14:03:00.000Z",
  "workspace": "acme",
  "rule": { "id": "rul_1a2b3c4d5e6f7a8b9c0d1e2f", "name": "Do not ship" },
  "event": "gate_blocked",
  "items": [
    {
      "project": "support",
      "kind": "run",
      "id": "run_20261026T140100_4f2a",
      "release_action": "BLOCK",
      "rules": [["faithfulness", "FAIL"]],
      "href": "https://oloproof.com/app/workspaces/acme/projects/support/runs/run_20261026T140100_4f2a"
    }
  ],
  "more": 0
}

Un elemento lleva ids, los estados de decisión del motor, la acción de release, el estado y el resultado de un job, el nivel, el umbral y el intervalo de un monitor tal como los escribió el motor, y enlaces. Nunca lleva la entrada, la salida, la respuesta esperada ni las etiquetas de un caso. Un envío contiene como mucho 50 elementos; more cuenta los eventos que quedan fuera.

Verificar un envío

Cada envío lleva tres cabeceras, en el formato de Standard Webhooks:

  • webhook-id: el id del envío, el mismo en cada reintento.
  • webhook-timestamp: cuándo se envió este intento, en segundos desde 1970.
  • webhook-signature: v1, y el HMAC-SHA256 en base64 de id.timestamp.body, con los bytes del secreto como clave (la parte tras whsec_, decodificada de base64).

Calcule la firma sobre el cuerpo en bruto tal como se recibió, compárela en tiempo constante y rechace una marca de tiempo que se aleje más de cinco minutos de su reloj.

import base64, hashlib, hmac, time

def verify(secret: str, headers: dict, body: bytes) -> bool:
    key = base64.b64decode(secret.removeprefix("whsec_"))
    msg = f"{headers['webhook-id']}.{headers['webhook-timestamp']}.".encode() + body
    expected = base64.b64encode(hmac.new(key, msg, hashlib.sha256).digest()).decode()
    fresh = abs(time.time() - int(headers["webhook-timestamp"])) <= 300
    given = [part.split(",", 1)[-1] for part in headers["webhook-signature"].split()]
    return fresh and any(hmac.compare_digest(expected, sig) for sig in given)
import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(secret, headers, body) {
  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const id = headers["webhook-id"], ts = headers["webhook-timestamp"];
  const expected = createHmac("sha256", key).update(`${id}.${ts}.${body}`).digest("base64");
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
  return fresh && headers["webhook-signature"].split(" ").some((part) => {
    const sig = Buffer.from(part.split(",").pop());
    return sig.length === expected.length && timingSafeEqual(sig, Buffer.from(expected));
  });
}

Las bibliotecas de Standard Webhooks verifican las mismas cabeceras.

Entrega

Responda con cualquier estado 2xx en menos de diez segundos. Cualquier otra cosa, incluida una redirección, es un intento fallido, que se reintenta tras 1, 5, 30, 120 y 360 minutos y después se abandona. Como una respuesta puede perderse después de que su servidor haya actuado, el mismo envío puede llegar dos veces: use webhook-id para descartar una repetición. Una regla se dispara como mucho una vez por evento, así que un id nuevo es un envío nuevo.

Antes de cada intento se vuelve a resolver el nombre del endpoint, y la petición solo va a una dirección pública: se rechazan las direcciones de loopback, privadas, link-local, compartidas, reservadas y de metadatos de la nube, así como las formas IPv6 que contienen una de ellas. La conexión se hace a la dirección comprobada, y el certificado se comprueba frente al nombre.

Slack

Un endpoint de Slack es una URL de incoming webhook. Recibe un mensaje breve en inglés, una línea por evento, con un enlace a cada resultado. Slack no comprueba las cabeceras de firma, que se envían de todos modos.