跳到主要内容

Webhook 事件

回调要求​

  • 必须使用公网 HTTPS 地址;平台不跟随重定向。
  • 收到请求后尽快完成签名校验并返回 HTTP 2xx。
  • 复杂业务放入异步队列,避免因为处理超时触发重复投递。
  • 使用 eventId 做全局幂等;消息类事件同时使用业务 messageId 做幂等。

请求头​

X-Open-App-Id
X-Open-Event-Id
X-Open-Event-Type
X-Open-Timestamp
X-Open-Signature

管理订阅(控制台 API)​

订阅的读写走控制台 API,使用开发者登录态而不是应用签名:

  • 基地址 https://imo.lanchr.com/api/open/console/v1(测试环境 https://im-open-test.lanchr.com/api/open/console/v1);
  • 请求头带 Authorization: Bearer <accessToken> 与 X-Open-Tenant-Id: <租户ID>,与其它控制台接口一致(见鉴权与签名 → 控制台 API);
  • 一次 PUT 需要一个事件类型;应用有多个事件类型时逐个配置。
接口方法说明
/apps/{appId}/subscriptionsGET列出全部订阅
/apps/{appId}/subscriptions/{eventType}PUT新建或更新某个事件类型的订阅,每次都重新生成 Secret
/apps/{appId}/subscriptions/{eventType}/verifyPOST触发一次回调地址验证

GET 返回订阅数组,每项包含 eventType、callbackUrl、status、failureCount、circuitState、updatedAt。

PUT 请求体:

字段类型必填说明
callbackUrlstring是回调地址,必须为公网 HTTPS 地址
statusstring否订阅状态,默认 ACTIVE;只有 ACTIVE 的订阅会收到投递

callbackUrl 会被校验:必须使用 HTTPS、主机名可解析、且解析结果全部是公网地址(拒绝回环、内网、链路本地等地址)。

获取 Webhook Secret​

Webhook Secret 不是应用凭证(appSecret),而是每个事件订阅各自独立的密钥,用于校验该订阅的投递签名。

PUT /apps/{appId}/subscriptions/{eventType} 在保存订阅的同时生成 Secret 并一次性返回:

{"credentialId": "<eventType>", "secret": "<Webhook-Secret>"}

credentialId 就是事件类型本身,secret 为 32 字节随机数的 Base64URL 编码(无 = 填充)。

⚠️ 每次 PUT 都会轮换 Secret,旧 Secret 立即失效——即使请求体内容完全相同。GET 永不回显 Secret。请务必在 PUT 的响应中把 Secret 落库;错过之后只能再 PUT 一次换取新 Secret,并同步更新你的验签配置。

签名​

Webhook 签名原文为:

eventId + "\n" + timestampMillis + "\n" + rawBody

使用 Webhook Secret(非应用 appSecret)计算 HMAC-SHA256,输出小写十六进制。必须基于原始请求体计算,不能先解析 JSON 再序列化。

伪代码:

const value = eventId + '\n' + timestamp + '\n' + rawBody
const signature = hmacSha256(webhookSecret, value).toLowerCaseHex()
// 与请求头 X-Open-Signature 做等时比较

校验时还应检查 X-Open-Timestamp 的时效性:建议与平台保持一致,接受前后 ±5 分钟的时间戳窗口,并对同一 eventId 去重以防重放。

验证回调地址​

用 POST /api/open/console/v1/apps/{appId}/subscriptions/{eventType}/verify 触发地址验证(无请求体)。平台会向该订阅的 callbackUrl 发送:

{"type": "url_verification", "challenge": "<uuid>"}

请求头为 Content-Type: application/json; charset=UTF-8,不携带任何签名请求头(没有 X-Open-*),因此无需验签。

你的服务只需识别 type == "url_verification" 并返回任意 2xx 即可通过验证。平台只看响应状态码,不比对回显内容,所以回显 challenge 只是良好的实现习惯:

{"challenge": "<收到的 challenge>"}

接口返回 {"code":"000000","value":true} 表示验证通过,value:false 表示平台没有收到 2xx。该订阅不存在或不是 ACTIVE 时调用失败。

响应和重试​

返回任意 2xx 表示接收成功。业务处理失败时不要返回 2xx 后静默丢弃,应保存事件并由业务队列重试。

投递使用以下超时:连接 3 秒、读取 8 秒,不跟随重定向。以下情况会重试:

  • 连接失败、超时等网络错误;
  • HTTP 408、409、425、429,或任意 5xx。

其它非 2xx(如 400、403、404)不会重试,事件会被直接判为投递失败并终止。最多重试 6 次,退避间隔依次为:

第 n 次重试123456
间隔10 秒30 秒2 分钟10 分钟30 分钟2 小时

用尽 6 次重试后事件进入终止状态,不再投递。

熔断:熔断按应用维度统计。10 秒滑动窗口内投递达到 5 次且失败率不低于 50% 时,该应用的 Webhook 投递被熔断 30 秒;这期间新事件不会被投递,而是延后 30 秒再排队。30 秒后放行 1 次探测请求,成功则恢复正常,失败则继续熔断。

投递记录与失败原因可在控制台「投递」页查看。

事件只在投递时存在 ACTIVE 订阅才会发出;此时若该事件类型没有有效订阅,事件会被直接丢弃,不会因为之后补建订阅而追发。请在应用上线前配置好订阅。

事件字段清单见事件模型。