自有平台对接

超然信机器人 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 只收不发;或作为离线兜底
可组合 A + C(在线走 WS 收消息,离线走 Hook;发消息用 WS)或 B + C(全程不用 WS)。 用户→机器人出站:机器人在线时只走 WS,离线时只走 Hook(互斥),不会双发。

准备工作

机器人的注册、令牌签发、启停等通常通过官方客户端完成。创建后你会拿到:

含义
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_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

RobotLogin

机器人通道没有 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
  • 顶层 typeclazz 一致(优先 Markdown)
  • 已实现约 30s 心跳与断线退避重连
  • 离线场景已配置 Hook 并完成验签 + 2xx
  • 了解 access 模式;subscribe 场景已处理 join
  • 收到 status=-1 能根据 msg 排查