Files
weixin_ClawBot_Bridge/docs/02-ilink-protocol.md
T

17 KiB
Raw Blame History

02 — iLink 协议完整参考

本文档是对 iLink Bot API 的完整整理,供实现协议层时对照查阅。 来源:腾讯官方包 @tencent-weixin/openclaw-weixin 的逆向分析、Java SDK 实现、多个第三方适配器实践。字段级细节以实测为准。

⚠️ 2026-09-08 实测 + 官方源码修正:本节 get_bot_qrcode(官方为 POST + local_token_list,非裸 GET)、登录请求头(官方还带 iLink-App-Id 与 iLink-App-ClientVersion 数字编码)、扫码状态机(不止 wait/scaned/confirmed)、以及两大实战坑(码服务端 TTL ≈60s、换码竞态)均已按腾讯官方包 @tencent-weixin/openclaw-weixin@2.4.8(src/auth/login-qr.ts、src/api/api.ts)源码 + 本仓库真机绑定实测修订。此前把它们写成裸 GET / 字符串 "1" / 三项状态,均已作废。

1. 基本信息

项 值
API 根地址 https://ilinkai.weixin.qq.com
CDN 地址 https://novac2c.cdn.weixin.qq.com/c2c
协议 HTTPS + JSON
收消息模式 HTTP 长轮询(无 Webhook,无需公网 IP)
鉴权 Authorization: Bearer <bot_token>

2. 端点总表

Endpoint Method 功能 需要鉴权
/ilink/bot/get_bot_qrcode POST(?bot_type=3) 获取登录二维码(官方带 body {"local_token_list":[...]};裸 GET 也能拿到码但非官方姿势) 否
/ilink/bot/get_qrcode_status GET 轮询扫码状态(?qrcode=xxx,可带 ?verify_code=xxx) 否
/ilink/bot/getupdates POST 长轮询收消息(核心) 是
/ilink/bot/sendmessage POST 发送消息(文本/图片/文件/视频/语音) 是
/ilink/bot/getconfig POST 获取配置(typing_ticket 等) 是
/ilink/bot/sendtyping POST 发送「正在输入」状态 是
/ilink/bot/getuploadurl POST 获取 CDN 预签名上传地址 是

3. 通用请求头

登录后所有请求的固定头:

Content-Type: application/json
AuthorizationType: ilink_bot_token
Authorization: Bearer <bot_token>
X-WECHAT-UIN: <base64(十进制字符串(随机uint32))>
iLink-App-Id: bot
iLink-App-ClientVersion: <数字编码>
  • X-WECHAT-UIN:每次请求生成一个随机 uint32 → 转十进制字符串 → base64 编码。每次都不一样,起防重放作用。
  • iLink-App-Id:固定值 "bot"(取自官方包 package.json 的 ilink_appid)。
  • iLink-App-ClientVersion:是数字,不是字符串 "1"!按官方 buildClientVersion 编码:(major&0xff)<<16 | (minor&0xff)<<8 | (patch&0xff)。例:2.4.8 → 0x020408 = 132104。
  • get_bot_qrcode / get_qrcode_status(预绑定阶段,尚未有 token)不带 Authorization,但仍带 iLink-App-Id / iLink-App-ClientVersion / Content-Type / AuthorizationType / X-WECHAT-UIN。
  • 可选 SKRouteTag 头(来自 openclaw.json 的 channels.<id>.routeTag,数字/字符串,可缺省;实测不会影响拿码内容)。

4. 登录流程(一次性扫码绑定)

⚠️ 本节为实测 + 官方源码修正版。两大实战坑(码 TTL / 换码竞态)见 4.5,务必读完再写登录循环。

4.1 时序

开发者                 iLink 服务器              微信用户
  │── POST get_bot_qrcode ─▶│                      │
  │    body {local_token_list:[]}                  │
  │◀── { qrcode, qrcode_img_content } ─┘          │
  │                                               │
  │                          │◀──── 用户扫码 ─────│
  │── GET get_qrcode_status ─▶│ (长轮询/定时轮询)  │
  │◀── { status:"scaned", ... }──┘                │
  │── GET get_qrcode_status ─▶│ (扫码成功后继续等)  │
  │◀── { status:"confirmed", bot_token, ... }──┘  │
  │
  ▼ 持久化 bot_token,后续所有请求 Bearer 鉴权

4.2 获取二维码

POST /ilink/bot/get_bot_qrcode?bot_type=3
Content-Type: application/json
iLink-App-Id: bot
iLink-App-ClientVersion: 132104
{ "local_token_list": [] }
  • local_token_list:本实例已登录的 bot token 列表(最多 10 个,取自本地已持久化的账号)。首次绑定传空数组 [](不要传 null);若已有已绑定的 bot,传其 token 列表,官方据此识别(可触发 binded_redirect,见 4.4)。
  • 不要单独依赖裸 GET:官方源码用 POST + local_token_list(apiPostFetch)。裸 GET 也能 HTTP 200 拿到码,但不是官方绑定姿势。实测两者返回的 qrcode_img_content 结构一致。

响应:

{
  "qrcode": "xxx",
  "qrcode_img_content": "https://liteapp.weixin.qq.com/q/7GiQu1?qrcode=<qrcode>&bot_type=3"
}
  • qrcode:QR 标识符(32 位 hex),轮询状态时用的参数。
  • qrcode_img_content:一个 liteapp 跳转 URL(liteapp.weixin.qq.com/q/...),不是一张可直接 <img src> 的图片。要拿它作为二维码内容用二维码库编码(qrcode-terminal / qrcode.toDataURL),微信扫开后会在微信内打开该 liteapp 页完成确认。注意该 URL 内嵌了 qrcode 参数,别把 qrcode 当独立二维码去扫(会扫出一串纯文本)。
  • bot_type=3 是官方包源码中硬编码的值,含义未公开,照抄即可。

4.3 轮询扫码状态

GET /ilink/bot/get_qrcode_status?qrcode=<qrcode>
# 若服务端要求配对码:
GET /ilink/bot/get_qrcode_status?qrcode=<qrcode>&verify_code=<用户输入的数字串>

状态流转(完整集合):

status 含义 处理
wait 还没被扫 继续轮询
scaned 已被微信扫,正在确认 继续等(重要:此时绝不换码,见 4.5)
scaned_but_redirect 扫码后 IDC 重定向,要切换 redirect_host 把后续轮询 base URL 改为 https://<redirect_host>,继续
need_verifycode 要用户输入手机微信显示的数字 提示用户输数字,把 verify_code 传回;输错重输
verify_code_blocked 配对码多次输入错误被封 结束本轮,重新开始(再次 get_bot_qrcode)
binded_redirect 该 bot 已绑定过本实例 视为已连接(等价成功,无需新凭据;保留本地已有 token)
confirmed 绑定成功 取 bot_token / ilink_bot_id / ilink_user_id / baseurl,持久化
expired 码已过期(服务端判定) 重新 get_bot_qrcode 换新码(注意「换码竞态」)

确认后响应(关键凭据):

{
  "status": "confirmed",
  "bot_token": "44b9b8ec@im.bot:060000b7...",
  "ilink_bot_id": "44b9b8ec@im.bot",
  "ilink_user_id": "o9cq809L...@im.wechat",
  "baseurl": "https://ilinkai.weixin.qq.com"
}
  • bot_token 是之后所有 API 调用的鉴权凭据,一次扫码长期有效(具体有效期官方未公布,社区观察为长期;过期会收到 errcode=-14,需重新扫码)
  • 建议把 bot_token、ilink_bot_id、ilink_user_id、baseurl 全部持久化
  • 轮询建议间隔 1s,整体超时给 5~8 分钟;轮询请求带 iLink-App-Id / iLink-App-ClientVersion 头,可作为长轮询 hold(官方 35s)处理。

4.4 get_qrcode_status 状态机总结

wait ──(用户扫)──▶ scaned ──(确认)──▶ confirmed
                     │   │
                     │   └─(IDC重定向)─▶ scaned_but_redirect ──▶ 切 host 继续
                     └─(要求数字)─▶ need_verifycode ──(输对)──▶ scaned
                                       └─(输错×N)──▶ verify_code_blocked ──▶ 重新开始
confirmed 的另一形态:binded_redirect(已绑过,直接成功)
任何时刻:expired(服务端把码作废,需换新码)

4.5 两大实战坑(务必读)

(1) 码的服务端 TTL ≈ 60 秒(实测:生成后约 67s 服务端自己返回 expired)。

  • 不是客户端 5 分钟 session 超时,而是服务端给二维码本身的寿命。
  • 用户必须在生成后约 60s 内用微信扫码。若超过窗口,「生成 → 扫」之间已过期。
  • 对策:要么把码做成"持续新鲜"(前端每 3045s 自动换新码 + 倒计时),要么保证扫码发生在码生成后的 60s 窗口内。做长期展示必须自动刷新。

(2) 「换码竞态」——已扫码未确认时换码会砍会话(本项目实测踩坑最久的一条,务必避开)。

  • 现象:用户在微信扫码后尚未确认时,若再次 get_bot_qrcode 换新码并 cancel 旧轮询,旧码的「扫码 → 确认」会话被彻底丢弃——微信端显示"连接成功",但后端轮询的是另一个码,永远收不到 confirmed。
  • 根因:二维码绑定是一个「码 → 会话」的一次性流程,换码即续一个新的、独立的会话,旧会话的确认事件不会出现在新码上。
  • 对策:一旦 get_qrcode_status 返回 scaned(或 scaned_but_redirect / need_verifycode),就绝不再换码,一直轮询到 confirmed / expired 为止;换码只允许在「码处于 wait(未扫)且已超过其 TTL」时发生。前端自动刷新码也一样——已扫到的码必须"冻结",禁止把它换掉。

4.6 一次性扫码绑定 Checklist

  • get_bot_qrcode 用 POST + body {"local_token_list":[]} + 头 iLink-App-Id: bot / iLink-App-ClientVersion:<数字>
  • 用 qrcode_img_content(liteapp URL)作为二维码内容编码后给用户扫,勿把 qrcode 直接当二维码
  • 轮询 get_qrcode_status 处理全部状态:wait/scaned/scaned_but_redirect/need_verifycode/verify_code_blocked/binded_redirect/confirmed/expired
  • 若收到 scaned_but_redirect,把后续轮询切到 https://<redirect_host>
  • 若收到 need_verifycode,提示用户输数字,用 verify_code 回传
  • scaned 后绝不换码,等 confirmed / expired
  • 码过期(expired)→ 换新码;但换码不砍掉已扫会话(见 4.5)
  • 持久化 bot_token / ilink_bot_id / ilink_user_id / baseurl

5. 收消息:长轮询

5.1 请求

POST /ilink/bot/getupdates
{
  "get_updates_buf": "<上次返回的游标,首次为空字符串>",
  "base_info": { "channel_version": "1.0.2" }
}

5.2 响应

{
  "ret": 0,
  "msgs": [ /* WeixinMessage[],见第 7 节 */ ],
  "get_updates_buf": "Ck1...",
  "longpolling_timeout_ms": 35000
}

5.3 机制要点

  • 服务器 hold 住连接最多约 30~35 秒(longpolling_timeout_ms 会告知),有新消息立即返回,无消息超时返回空 msgs
  • HTTP 客户端超时必须设得比 longpolling_timeout_ms 大(建议 +10s 余量)
  • get_updates_buf 是同步游标(类似 Telegram getUpdates 的 offset):
    • 每次请求带上次返回的值
    • 收到响应后立刻持久化,否则重启后会重复收消息
    • 返回中没有该字段时保持旧值
  • HTTP 超时/网络错误视为「本轮无新消息」,直接重试,不要中断循环

5.4 失败恢复与退避(来自 Java SDK 的实战策略)

情况 处理
errcode = -14(会话过期 / token 失效) 该账号进入暂停窗口(SDK 默认 1 小时),期间停止轮询和发送;提示用户重新扫码
其他失败 2s 后快速重试;连续失败 3 次后进入 30s 退避
HTTP 超时 视为无新消息,立即进入下一轮
响应带 longpolling_timeout_ms 动态调整下一轮轮询超时

6. 发消息

POST /ilink/bot/sendmessage
{
  "msg": {
    "to_user_id": "o9cq809L...@im.wechat",
    "client_id": "unique-id-123",
    "message_type": 2,
    "message_state": 2,
    "context_token": "CK3E...",
    "item_list": [
      { "type": 1, "text_item": { "text": "你好!我是你的 AI。" } }
    ]
  }
}

字段说明:

字段 说明
to_user_id 收件人,取入站消息的 from_user_id
client_id 客户端生成的去重 ID(如 UUID),防止重发
message_type 2 = Bot 发出的消息(入站用户消息是 1)
message_state 2 = FINISH 完成态(非流式)。协议还有 NEW(0?) → GENERATING → FINISH 的三段式状态,可用于流式分片发送(Java SDK 的 sendTextStream 即分片发送,但注意那不是 LLM token 流,而是把长文本切成多条消息)
context_token 必填,从要回复的那条入站消息原样取出
item_list 消息体,文本见上;媒体 item 构造见 03 文档

7. 消息结构(WeixinMessage)

入站消息核心字段:

{
  "from_user_id": "o9cq800kum_xxx@im.wechat",
  "to_user_id": "e06c1ceea05e@im.bot",
  "message_type": 1,
  "message_state": 2,
  "context_token": "AARzJWAFAAABAAAAAAAp...",
  "item_list": [
    { "type": 1, "text_item": { "text": "你好" } }
  ]
}

7.1 ID 格式规律

  • 用户 ID:xxx@im.wechat
  • Bot ID:xxx@im.bot

7.2 常量表

字段 值 含义
message_type 1 用户消息(入站)
message_type 2 Bot 消息(出站)
message_state 2 FINISH(完整消息)
item.type 1 文本(text_item.text)
item.type 2 图片(CDN 加密存储)
item.type 3 语音(silk 编码,voice_item.text 附 ASR 转写文字)
item.type 4 文件附件
item.type 5 视频

媒体类 item 的详细字段(CDN 引用、aes_key 等)见 03-messages-and-media.md。

8. context_token:对话关联的核心

整个协议里最关键、最容易踩坑的细节:

  1. 每条入站消息都带 context_token,回复时必须原样带上,否则消息无法关联到正确的对话窗口
  2. 每条入站消息都应刷新本地保存的 token(Java SDK 的做法:FileContextTokenStore 按对话持久化,每条入站消息更新),这样主动发消息时也有最新 token 可用
  3. 有资料称:用户最后一条消息超过 24 小时后,bot 的消息可能送达不到;在窗口内可用 context_token 主动追发消息。做定时推送类功能时必须考虑这个窗口(⚠️ 建议实测确认)

9. 输入中状态(typing)

POST /ilink/bot/getconfig    → 获取 typing_ticket
POST /ilink/bot/sendtyping   → 发送「正在输入」

用途:AI 后端处理耗时较长时,先发个 typing 让用户知道 bot 在干活。非必需,第一版可以不做。

10. 错误处理备忘

现象 含义 处理
errcode = -14 会话/token 过期 暂停轮询与发送,走重新扫码流程
ret != 0 业务错误 记录完整响应,按退避策略重试
HTTP 层超时 长轮询正常超时或网络抖动 直接重试
重复收到同一条消息 get_updates_buf 没更新/没持久化 检查游标保存逻辑;应用层按消息 ID 去重兜底

11. 协议层 Checklist(实现自查)

  • 扫码登录:get_bot_qrcode(POST + local_token_list)→ 编码 qrcode_img_content → 轮询 get_qrcode_status 到 confirmed
  • 持久化:bot_token / ilink_bot_id / ilink_user_id / baseurl / get_updates_buf / 每对话 context_token
  • 请求头:Authorization、AuthorizationType、随机 X-WECHAT-UIN、iLink-App-Id、iLink-App-ClientVersion(数字)
  • 扫码状态机:处理 scaned / scaned_but_redirect / need_verifycode / verify_code_blocked / binded_redirect / expired
  • scaned 后不换码;码 TTL ~60s,需保证"生成 → 扫"在窗口内(或自动换新码但不砍已扫会话)
  • 长轮询循环:超时 > 服务端 timeout、游标即时保存、异常不中断
  • 回复带 context_token、带唯一 client_id
  • errcode=-14 的暂停与重新登录路径
  • 退避重试策略

参考资料