跳到主要内容

指南

Webhooks

webhook 端点接收工作区的警报规则路由给它的内容:被阻止的发布、等待人来处理的结果、已结束的托管运行、生产监控的警报。每次投递都是用端点密钥签名的 JSON 正文,因此你的服务器在处理之前可以核实它来自你的工作区。

添加端点

在工作区的 Integrations 页面上,Owner 或 Admin 用一个名称和端口 443 或 8443 上的公开 https URL 添加签名 webhook。随后页面会显示端点的签名密钥,它以 whsec_ 开头。请当时就复制:它会加密存储,不会再次显示。要更换它,请删除端点后重新添加。

然后,在 Alerts 页面上,把该端点选为一条或多条规则的渠道。“Send a test”会发送一次 oloproof.test 投递,并显示你的服务器返回的状态。

发送的内容

{
  "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
}

一个条目包含 id、引擎的决定状态、发布动作、作业的状态和结果、按引擎写下的监控水平、阈值和区间,以及链接。它从不包含用例的输入、输出、预期答案或标签。一次投递最多包含 50 个条目;more 统计超出部分的事件数。

验证投递

每次投递都带有三个 Standard Webhooks 格式的标头:

  • webhook-id:投递的 id,每次重试都相同。
  • webhook-timestamp:本次尝试发送的时间,以自 1970 年起的秒数表示。
  • webhook-signature:v1, 加上 id.timestamp.body 的 base64 HMAC-SHA256,以密钥的字节(whsec_ 之后的部分,经 base64 解码)为键。

请对收到的原始正文计算签名,以常数时间比较,并拒绝与你的时钟相差超过五分钟的时间戳。

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));
  });
}

Standard Webhooks 的库验证的是同样的标头。

投递

请在十秒内以任意 2xx 状态应答。其他任何应答(包括重定向)都算失败的尝试,会在 1、5、30、120 和 360 分钟后重试,之后放弃。由于应答可能在你的服务器处理之后丢失,同一次投递可能到达两次:请用 webhook-id 丢弃重复。一条规则对每个事件最多触发一次,所以新的 id 就是新的投递。

每次尝试之前都会重新解析端点的名称,请求只发往公开地址:环回、私有、链路本地、共享、保留和云元数据地址都会被拒绝,携带其中之一的 IPv6 形式也一样。连接建立在经过检查的地址上,证书则按名称进行检查。

Slack

Slack 端点是一个传入 webhook URL。它接收一条简短的英文消息,每个事件一行,附有指向每个结果的链接。Slack 不检查签名标头,但标头照样会发送。