超然信机器人 IM 协议
面向首次接入的第三方开发者:机器人与真实用户同级,支持一对一单聊。本文描述外部可见协议,不含服务端实现细节。
机器人能做什么
- 给指定用户发消息(文本 / Markdown、图片、文件、@、卡片等)
- 收到用户发来的消息(实时 WebSocket,或离线 Hook)
- 多端同步:同一用户在不同设备登录时,机器人发出的消息都会到达
占位符约定:{your-origin}、{user-uuid}、rbt_…。机器人 uuid 不预先提供,从 WS 握手的 RobotLogin 取得。
三种接入路径
| 路径 | 形态 | 适用场景 |
|---|---|---|
| A. WebSocket 双向 | 长连接 + JSON 帧 | 推荐。双向实时 |
| B. REST 推送 | POST /bot/api/v1/message/push |
只发不收;或不想维护长连接 |
| C. HTTP Hook | 你提供 HTTP 服务端,平台反向 POST | 只收不发;或作为离线兜底 |
准备工作
机器人的注册、令牌签发、启停等通常通过官方客户端完成。创建后你会拿到:
| 项 | 含义 |
|---|---|
rbt_* |
机器人访问 token。用于 WS Header / REST 推送 / 节点列表鉴权。通常只显示一次 |
| 机器人 uuid | 协议里机器人方的 from / to。对接前不必预知,从 RobotLogin.data.robot 取得 |
| owner | 创建者 user uuid(排查权限与归属用) |
| access | private / public / subscribe,见下方访问模式 |
| Hook secret | 若启用 Hook,需保存 secret 用于验签 |
程序化管理(创建 / Hook / token)走 /bot/api/v1/*,使用用户令牌鉴权,不要传 rbt_*。
节点发现
多节点 / 集群场景下,先拉取可连接节点:
GET {your-origin}/im/api/v1/robot/servers
Authorization: Bearer rbt_REPLACE_ME_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx或:
POST {your-origin}/im/api/v1/robot/servers
X-Robot-Token: rbt_REPLACE_ME_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx响应示例:
{
"code": 0,
"msg": "success",
"data": [
{
"host": "im-node-1.example.com",
"port": 9000,
"inter": 9000,
"path": "/",
"media": "/media",
"robot": "/robot"
}
]
}任选一个节点连接 ws://{host}:{inter}{robot}(robot 默认 /robot)。单节点部署可直接使用已知 host:port。
WebSocket 双向(推荐)
握手鉴权
必须用 Header 传 rbt_*,禁止 URL Query 传 token(否则 400):
GET /robot HTTP/1.1
Host: im.example.com:9000
Upgrade: websocket
Connection: Upgrade
# 二选一:
Authorization: Bearer rbt_REPLACE_ME_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# 或
X-Robot-Token: rbt_REPLACE_ME_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxRobotLogin
机器人通道没有 Login 帧。握手成功后约几十毫秒,服务端主动下发:
{
"type": "RobotLogin",
"data": {
"ok": true,
"robot": "{robot-uuid}",
"owner": "{owner-uuid}",
"msg": ""
}
}请缓存 data.robot,之后所有发消息帧的 from 必须使用该值。
心跳
建议每 30 秒发送一次(服务端约 15 分钟无业务帧断开):
{
"type": "Heart",
"data": { "time": 1714000000000 }
}机器人 → 用户
通道始终为明文 JSON。顶层 type 为内容类型(如 Markdown),须与 data.clazz 一致,不要写成 "Msg"。
{
"type": "Markdown",
"data": {
"uuid": "msg-REPLACE-ME-001",
"from": "{robot-uuid}",
"to": "{user-uuid}",
"clazz": "Markdown",
"role": "robot",
"content": { "text": "你好,已收到你的工单 **#A001**" }
}
}成功回执 status=100;失败 status=-1(如 发送方必须为当前登录机器人 表示 from 错误)。
用户 → 机器人
机器人 WS 在线时,收到:
{
"type": "RobotEvent",
"data": {
"schema": "2.0",
"header": {
"event_id": "evt-…",
"event_type": "im.message.receive_v1",
"create_time": "1714000000",
"robot_id": "{robot-uuid}"
},
"event": {
"sender": { "user_id": "{user-uuid}" },
"message": {
"message_id": "msg-…",
"msg_type": "text",
"content": { "text": "在吗?" }
}
}
}
}离线期间用户消息不会补推到 WS,请用 Hook 兜底。断线请指数退避重连(约 1s→30s);401 勿盲目重连,需重新签发 token。
REST 推送
仅需发消息或不想维护长连接时使用:
POST {your-origin}/bot/api/v1/message/push
Content-Type: application/json;charset=UTF-8
X-Robot-Token: rbt_REPLACE_ME_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx写法 A:完整 IM 帧(推荐)
{
"data": {
"type": "im.message.send",
"data": "{\"type\":\"Markdown\",\"data\":{\"from\":\"{robot-uuid}\",\"to\":\"{user-uuid}\",\"clazz\":\"Markdown\",\"content\":{\"text\":\"这是通过 REST 发的 **Markdown** 文本\"}}}",
"messageId": "msg-REPLACE-ME-002"
}
}写法 B:简化体
{
"data": {
"type": "im.message.send",
"data": {
"to_user": "{user-uuid}",
"text": "已签收"
}
}
}code=0 && queued=true 表示入队成功,投递异步完成。
HTTP Hook 回调
适合离线兜底或纯服务端后台。创建 Hook(用户令牌):
POST {your-origin}/bot/api/v1/hook/create
Content-Type: application/json;charset=UTF-8
Token: {user-token}
{
"data": {
"robot": "{robot-uuid}",
"name": "消息监听",
"callbackUrl": "https://your-server.example.com/robot-hook",
"secret": "REPLACE_ME_WITH_YOUR_RANDOM_SECRET",
"events": "im.message.receive"
}
}收到的回调
POST /robot-hook
Content-Type: application/json;charset=UTF-8
X-Robot-Event: im.message.receive
X-Robot-Timestamp: 1714000000123
X-Robot-Signature: <hex>Body 结构与 WS RobotEvent 的载荷一致(无外层 Data 包装)。
必须验签
payload_to_sign = X-Robot-Timestamp + "." + <原始请求体字符串>
expected = HMAC_SHA256_hex(key=secret, message=payload_to_sign)
# X-Robot-Signature 须等于 expected(小写 hex)- 必须用原始 body bytes 计算,勿反序列化后再拼
- 返回 HTTP 2xx 视为成功;非 2xx 会重试,耗尽后入死信
- 建议 ≤ 3 秒内响应
访问模式
| 模式 | 说明 |
|---|---|
private |
仅机器人 owner 可对话 |
public |
任意有效 IM 用户 |
subscribe |
须先 POST /im/api/v1/robot/join 建立会话关系后方可发消息 |
加入示例(用户登录态):
POST {your-origin}/im/api/v1/robot/join
Authorization: Bearer {user-token}
Content-Type: application/json
{ "data": "{robot-uuid}" }限流与自检清单
| 项 | 值 |
|---|---|
| REST 载荷大小 | JSON ≤ 256KB(UTF-8) |
| 推送限流 | 默认每机器人 60 次/分钟 |
| 幂等键 | messageId ≥ 8 位 [a-zA-Z0-9._-],24 小时内去重 |
上线前自检
- 已妥善保存
rbt_*,且未写入公开仓库 - WS 用 Header 鉴权,未使用 Query 传 token
- 已解析
RobotLogin,发消息from为机器人 uuid - 顶层
type与clazz一致(优先 Markdown) - 已实现约 30s 心跳与断线退避重连
- 离线场景已配置 Hook 并完成验签 + 2xx
- 了解 access 模式;subscribe 场景已处理 join
- 收到
status=-1能根据msg排查