Skip to content

事件订阅 ​

组织发生成员/团队/组织本身变动、你的应用被安装/卸载/调整授权、或你发出的通知 被成员读到时,机灵兔会主动 POST 通知你的后端(webhook)——不用轮询通讯录, 也不用让管理员手动同步。

1. 配置回调地址 ​

在客户端「组织」→「应用」→ 应用设置 →「事件订阅」里填回调地址(https)并 勾选关心的事件,点「保存并验证回调地址」。回调目标必须是公网可达的服务器 (域名需解析到公网地址;环回/内网/链路本地地址会被平台直接拒绝——这是 SSRF 防线,本地联调请用隧道或让服务端管理员开 ORG_APP_CALLBACK_ALLOW_PRIVATE_IP)。

保存前平台会向该地址发一次验证请求:

json
// POST 你的回调地址
{ "type": "url_verification", "challenge": "a1b2c3…" }

你的端点必须返回 HTTP 200 + 原样回显 challenge:

json
{ "challenge": "a1b2c3…" }

回显一致才保存成功。留空回调地址即关闭订阅。

2. 事件推送格式 ​

json
// POST 你的回调地址
{
  "event_id": "e8f0…",
  "type": "org.member.joined",
  "occurred_at": "2026-08-25T10:00:00+00:00",
  "data": { "org_id": "org_1", "user_id": "u_123", "nickname": "张三" }
}

请求头:

Header说明
X-Jlt-Event事件类型(见下表)
X-Jlt-Event-Id事件 id(幂等去重用,同一事件重试时不变)
X-Jlt-Timestamp发送时间(unix 秒)
X-Jlt-Signaturehex(HmacSHA256(app_secret, "<timestamp>." + 请求体原文))

验签(务必做):用 app_secret 对 <X-Jlt-Timestamp> 的字符串值 + "." + 请求体 原始字节(不要 re-serialize 过的 JSON)复算 HmacSHA256,与 X-Jlt-Signature 常量时间比对;并拒绝时间戳过老(如超过 1 小时)的请求防重放。

返回 2xx 表示接收成功;非 2xx 或超时(10s)会自动重试,退避间隔 30s → 2m → 10m → 1h,共 5 次尝试后标记失败(终态)。同一事件重试时 X-Jlt-Event-Id 不变——建议按它做幂等。

3. 可订阅事件(15 种) ​

路由语义:org.* 推给你所在组织的自建应用;app.installed / uninstalled / scopes.granted 推给应用发布方(市场应用);app.message.* 只推给发送消息的 那个应用本身。

成员 ​

事件data 字段说明
org.member.joinedorg_id, user_id, nickname成员加入你的组织
org.member.leftorg_id, user_id成员离开(被移除或主动退出;其团队关系被级联清理,不另发团队事件)
org.member.updatedorg_id, user_id, changes成员资料或组织角色变更;changes 只含实际变更的字段:role / nickname / title / face_url(对组织应用可见的三个资料字段)。转让超管会对新老 owner 各推一条

团队 ​

事件data 字段说明
org.team.createdorg_id, team_id, name, parent_team_id团队创建
org.team.updatedorg_id, team_id, changes团队挂靠上级变更(changes.parent_team_id)
org.team.deletedorg_id, team_id, name团队删除
org.team.members.changedorg_id, team_id, added, removed, role_changed, added_count, removed_count, role_changed_count团队成员批量替换/角色调整的完整 diff。数组各截断 100 条,全量计数看 *_count 字段;added 是 [{user_id, role}],removed 是 [user_id],role_changed 是 [{user_id, role}]

组织 ​

事件data 字段说明
org.updatedorg_id, changes组织资料变更(changes 为变更字段字典)
org.dissolvedorg_id, reason?, delisted组织解散(审批通过或平台强制归档,后者带 reason)。此时 app token 已即时失效,但本事件照常投递——你的后端应据此停止调用该组织的开放接口
org.unarchivedorg_id组织被平台恢复,token 重新可用

应用市场(发布方) ​

事件data 字段说明
app.installedorg_id, granted_scopes你的应用被某组织安装(市场应用)
app.uninstalledorg_id你的应用被某组织卸载
app.scopes.grantedorg_id, granted_scopes安装方组织调整了授予的能力

消息通知(发送方应用) ​

事件data 字段说明
app.message.sentorg_id, results你经 /open/im/send 发通知后的逐收件人结果回执(results 与接口同步返回一致);适合同步失败落库/告警
app.message.readorg_id, user_id, messages, messages_count成员读到你发出的通知(已读回执)。messages 为 [{server_msg_id, sent_at}](截断 100 条,全量看 messages_count)。语义是会话级已读:成员打开了与「应用通知」账号的会话即视为该成员名下所有未读通知全部读到。未订阅 app.message.read 的应用不产生此事件

已读回执是尽力语义:30 天内未被打开会话的通知不再追溯(关联行过期清理)。

4. 接收端示例(Node.js / Express) ​

js
import crypto from "node:crypto";
import express from "express";

const APP_SECRET = process.env.JLT_APP_SECRET;

const app = express();
// 注意:验签要的是请求体原始字节,先拿 raw body 再 parse。
app.use("/jlt/events", express.raw({ type: "application/json" }), (req, res) => {
  const ts = req.header("X-Jlt-Timestamp") || "";
  const sig = req.header("X-Jlt-Signature") || "";
  const expected = crypto
    .createHmac("sha256", APP_SECRET)
    .update(ts + "." + req.body.toString("utf8"))
    .digest("hex");
  if (!sig || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return res.status(401).json({ error: "bad signature" });
  }
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 3600) {
    return res.status(401).json({ error: "stale" });
  }

  const event = JSON.parse(req.body.toString("utf8"));

  // ① 配置回调时的验证请求:原样回显 challenge
  if (event.type === "url_verification") {
    return res.json({ challenge: event.challenge });
  }

  // ② 按 event_id 幂等处理事件
  console.log("收到事件", event.type, event.data);

  // ③ 尽快 2xx;耗时逻辑丢队列异步做,超时(10s)会被判定失败重试
  res.json({ ok: true });
});

app.listen(3000);

5. 常见问题 ​

  • 保存时报「回调地址验证失败」:端点没有回显 challenge,或返回了非 200; 先用 curl 自测(见下)。
  • 收不到事件:在应用设置里看「待投递 N · 失败 N」概况;失败通常是端点 5xx/超时。修好端点后点**「重投失败事件」**,已终态失败的投递会重新排队 (成功过的不重发)。
  • 密钥轮换后:旧签名验不过是预期——新事件用新 secret 签名,接收端记得同步。
bash
# 本地自测回调端点
curl -s http://localhost:3000/jlt/events \
  -H 'Content-Type: application/json' -H 'X-Jlt-Timestamp: 1700000000' -H 'X-Jlt-Signature: x' \
  -d '{"type":"url_verification","challenge":"abc"}'
# → {"challenge":"abc"}

机灵兔开放平台 · 组织应用开发者文档