企业微信 SCRM SDK 开放接口文档
1. 概述
我方提供企业微信 SCRM SDK 网关,让贵方无需部署任何服务即可:
- 接收机器人产生的一切业务数据(消息、会话、客户、群、群成员、标签、朋友圈、上下线、历史消息);
- 驱动机器人执行指令(发消息/撤回、建群/群管理、加好友/改备注/打标签、朋友圈、标已读、同步数据、拉历史、僵尸粉检测与重加、语音转文字、群二维码、入群审批、扫码进群、标签管理、拉 APP 日志)。
接入形态:
企微机器人(我方APP) ──TCP──► 我方网关 ──HTTPS POST──► 贵方回调地址(事件推送) 贵方后端 ──HTTPS POST──► 我方网关指令API(/sdk/v2) ──TCP──► 企微机器人(指令下发)
通信两端皆为 HTTPS + JSON。
1.1 产品形态与三端分工
| 端 | 部署位置 | 职责 |
|---|---|---|
| 设备端(机器人 APP) | 贵方自备的安卓手机 | 内嵌企微 hook 感知企微内一切动作,执行网关下发的全部指令(收发消息、建群、加好友、朋友圈、发单等);本地持久化行为配置,断线自愈 |
| SDK 网关(我方) | 我方云上 | Netty 长连接枢纽:设备接入认证与归属、指令下发、回执与业务数据上报;翻译为标准事件经 HTTPS 推给贵方;对外提供指令 API / 文件上传 / 运维接口 |
| 贵方后端 | 贵方 | 实现 callbackUrl 接收事件(§5)+ 调用指令 API 驱动机器人(§6) |
appId/appSecret 凭证,其余由我方提供。2. 接入准备
2.1 获取凭证
| 项 | 说明 | 获取方式 |
|---|---|---|
appId | 客户唯一身份标识(公开,用于鉴权 header 与设备端登录账户) | 我方管理端创建 SDK 客户时下发 |
appSecret | 签名密钥(仅创建/重置时展示一次,请立即保存) | 同上;丢失需联系我方重置 |
callbackUrl | 事件推送地址(需公网/内网穿透可达,我方主动 POST) | 创建时登记,可后续修改 |
appId 双重身份:既是贵方调用指令 API 的鉴权标识(§3.2 / §4),也是设备端 APP 的登录账户(§2.2)——一个 appId 名下可挂多台设备/多个机器人(受 deviceLimit 配额限制)。2.2 设备侧搭建(自备设备 → 机器人上线)
机器人 = 跑在我方 APP(内嵌企微 hook 模块)中的企业微信账号。每台设备按以下步骤一次性开通,完成后该机器人即自动进入可对接状态,后续增减设备由贵方自行操作,无需我方介入。
一、设备与环境要求
| 项 | 要求 |
|---|---|
| 手机 | 安卓真机(已 ROOT / Magisk 环境,已安装 LSPosed 框架) |
| 企业微信 | 指定版本 WeCom 5.0.11 / 5.0.12(务必使用我方提供的安装包,版本不符 hook 不生效) |
| 我方 APP | 机器人 APK(随交付提供,包名 com.easy.crm) |
| 网络 | 可访问公网:设备登录控制面(APP 内置地址)+ 网关 TCP 接入地址(随登录下发) |
| 企微账号 | 待托管的机器人企业微信账号(可在设备企微上正常登录) |
二、开通步骤(每台设备一次)
- 安装企业微信:安装指定版本(5.0.11 / 5.0.12),先不登录。
- 安装机器人 APP:安装我方 APK 并授予后台运行 / 自启动 / 电池白名单权限(保活依赖)。
- 激活 Xposed 模块:LSPosed → 模块 → 勾选我方 APP → 作用域勾选企业微信 → 重启手机。
- 登录机器人 APP:打开 APP,在登录框输入我方下发的
appId(账户) → 点登录。APP 向控制面换取本租户接入配置(网关 TCP 地址IP[:PORT]、媒体上传地址、转链地址)并自动保存,随后进入常驻后台服务。 - 登录企微账号:在设备上的企业微信内登录机器人企微账号(正常企微登录流程,验证码/设备验证按企微提示操作)。APP 会自动拉起并保活企微。
- 确认上线:贵方事件端点收到
robot.online事件(§5.6.5),或调GET /robots(§6.2.1)看到online=true—— 该机器人即完成归属、进入可对接状态。此后 APP 断线自动重连(10~40 秒随机间隔)、企微被杀自动拉起,无需人工值守。
三、归属与对应关系(必读)
- 账户 ↔ 设备:一个
appId可同时登录多台设备(受deviceLimit配额限制),新增设备重复上述步骤即可。 - 机器人 ↔ 设备:一台设备同一时间承载一个企微账号。设备认证时上报(租户=
appId、企微账号 ID、设备指纹),网关据此把wechatId归属到贵方appId名下——归属成功后,指令 API 的归属校验(2004)与事件推送才对该机器人生效。 - 设备指纹:踢线闩锁按设备维度判定——同一台手机换绑其他企微号登录仍会被拦截,换绑前请联系我方处理。
- 首连依赖控制面:设备首次 TCP 接入需控制面登记归属(5 秒超时 fail-closed),控制面不可达时设备无法上线,请保障网络可达性。
四、设备状态排障速查
| 现象 | 排查动作 |
|---|---|
| APP 登录报错 | 确认账户为完整 appId;确认设备可访问控制面与公网 |
robot.online 迟迟不来 | LSPosed 模块未激活或作用域未勾选企微;企微版本不符;网关 TCP 地址端口不通 |
收到 robot.offline 且 reason=banned | 企微账号被封(§5.6.5 reason 分类表),停止该账号自动业务并人工处理 |
| 机器人在线但指令无响应 | 先 GET /robots/{wechatId}/status(§6.2.2)确认在线;在线仍无响应 → 调拉取 APP 日志(§6.12.3)取证 |
| 设备重装/清数据/换机后发单停止 | 本地配置清零,重新下发 /sender-config(§6.10.1「配置持久性与重下发责任」) |
2.3 接入三步骤
- 从我方管理端获取
appId/appSecret(appSecret 仅此一次展示,请立即保存到贵方密钥库),并登记callbackUrl。 - 按 §2.2 完成设备侧搭建,等待
robot.online事件(以收到它作为向该机器人发指令的可靠信号,见 §5.6.0)。 - 在贵方后端实现一个 HTTPS 端点接收事件推送(见 第 5 章),并调用指令 API 驱动机器人(见 第 6 章)。
GET /sdk/v2/debug/events 轮询事件,即可无需回调地址完成"开通 → 上线 → 下发 → 回执"全链路验证,联调通过后再实现回调端点切换为实时推送。2.4 验收清单
见 §8.3 自测验收清单。
3. 调用方式(通用约定)
3.1 服务域名
- 指令 API Base URL:
{网关地址}/sdk/v2(网关地址以我方下发为准,示例http://39.102.117.196:9100)。 - 事件推送地址:贵方登记的
callbackUrl。
3.2 公共请求头
指令 API(贵方 → 我方)与事件推送(我方 → 贵方)使用同一套签名,请求都带以下 3 个头:
| Header | 说明 |
|---|---|
Authorization | Bearer {appId}(身份标识) |
X-Icaiy-Timestamp | 当前毫秒级 Unix 时间戳,偏差超过 ±5 分钟拒绝(防重放) |
X-Icaiy-Signature | 请求签名,算法见 第 4 章 |
事件推送额外携带 2 个头(见 5.2):X-Icaiy-Event(事件类型)、X-Icaiy-App-Id(appId)。指令 API 的请求体为 application/json。
/sdk/v2 端点响应恒携带 X-Icaiy-Request-Id 响应头——贵方可传入该请求头(≤64 字符),缺省网关自动生成 UUID 并回显。报障时提供 request id 即可关联我方网关全链路日志。3.3 统一响应信封(指令 API)
指令 API 的 HTTP 状态码恒为 200,业务判断以 body 中的 code 为准:
{
"code": 200,
"message": "操作成功",
"data": { }
}| 字段 | 类型 | 说明 |
|---|---|---|
| code | int | 200=受理成功;非 200=失败(见 第 7 章) |
| message | string | 成功为 操作成功;错误时为人话原因(具体到字段) |
| data | object | 业务数据;查询类接口返回数组/对象;错误时缺省 |
3.4 幂等与回执模型(必读)
所有指令均为异步执行(查询型端点除外),"受理成功"≠"执行成功"。
- 幂等:指令请求携带
clientCmdId(贵方自定义唯一 ID)。相同clientCmdId重复提交将被拒绝(409),不会重复下发。幂等键分两档:- 必填档(回执需 taskId/clientCmdId 精确关联的指令端点,如发消息/改备注/朋友圈操作):缺失直接
400; - 可选档(受理即走的能力端点,如语音转文字/僵尸粉检测/群二维码/标签管理/拉日志):不传直接执行,传入则幂等。
- 必填档(回执需 taskId/clientCmdId 精确关联的指令端点,如发消息/改备注/朋友圈操作):缺失直接
- 回执分三层:
| 回执层 | 形式 | 说明 |
|---|---|---|
| ① 受理回执 | 指令 HTTP 同步响应 | code=200 只代表指令已校验、限流通过并下发到设备;data.receiptEvent/receiptMode 标明后续回执形式 |
| ② 任务回执 | task.result / message.receipt 事件 | 设备端执行有结果语义任务后的成败上报(receiptMode=task) |
| ③ 业务事件回执 | 业务事件流 | 业务生效本身即回执(receiptMode=event):发消息→message.receipt、撤回→message.revoked、建群→group.add、同步→数据事件流 |
- 回执模型三态(每个指令端点成功响应的
data中返回):
| receiptMode | 含义 | receiptEvent 示例 |
|---|---|---|
task | 有 taskId 精确关联回执:task.result / message.receipt 事件回传下发时的 taskId 与 clientCmdId | task.result、message.receipt |
event | 无专属任务回执,结果经事件流推送感知(事件携带下发 taskId 供关联) | robot.qrcode、history.batch、voice.transcript、logs.pulled 等 |
none | 仅下发受理、无任务级回执:结果按业务数据变化判断(撤回/建群及通知型指令,诚实披露见 §6.13.2) | null |
clientCmdId 是贯穿指令生命周期的关联键——指令受理时传入 → 回执事件 payload 回填(message.receipt / task.result 携带该值)→ 成功响应 data 回显。贵方以它做幂等去重与状态回填。无回执能力端点的完整清单与对接建议见 §6.13.2。3.5 事件投递语义
- 至少一次投递,贵方必须按
eventId幂等去重。 message.*类事件按单个机器人严格有序,其余事件不保证顺序。- 投递失败会指数退避重试(见 5.5)。
3.6 在线调试(Swagger,免代码测试)
拿到 appId/appSecret 后无需写一行代码,即可在浏览器中完成全部指令 API 的联调测试:
http://{gateway-host}:9100/swagger-debug.html ← 推荐入口(自动签名,可直接 Execute)
http://{gateway-host}:9100/swagger-ui.html ← 仅浏览文档(静态鉴权头,试调会 401)
http://{gateway-host}:9100/v3/api-docs ← OpenAPI3 原始 JSON(可导入 Apifox/Postman)调试页使用步骤:
- 打开
/swagger-debug.html,在页面顶部「凭证配置」填入appId与appSecret,点保存(存于浏览器 localStorage,仅本机)。 - 展开任意端点 → Try it out → 填参数 → Execute——签名由页面内置的 HMAC-SHA256 拦截器自动计算(
Base64(HMAC-SHA256(appSecret, timestamp + "\n" + rawBody)),时间戳取本机当前毫秒值),无需手工填任何鉴权头。 - 点页面上方「事件观察器」的开始观测:页面每 3 秒轮询
GET /sdk/v2/debug/events,实时展示robot.online/message.receipt/task.result等事件(点击行展开完整 JSON)。 - 观察响应与参数回显;POST 端点签名含请求体原文,修改参数后直接再次 Execute 即可。
无代码测试闭环(无需回调地址):事件推送(第 5 章)需要贵方拥有公网可达的 HTTPS 端点。为让测试用户在尚未实现回调端点时也能验证完整链路,网关在事件产生时同步写入一份调试事件缓冲(与 callbackUrl 是否配置、回调投递成败完全解耦,内容即实际推送的事件原文),测试用户经 §6.12.4 GET /sdk/v2/debug/events 轮询即可观测事件流。完整闭环:调试页发指令 → 观察器看到对应回执事件 → 验证通过后再实现回调端点接收实时推送。
注意:
- 调试页按同一签名契约工作,±5 分钟时间戳窗口、防重放(同签名 5 分钟内仅受理一次)与正式调用完全一致——用调试页测通即为对接成功。
- 调试页仅是签名辅助工具,
appSecret不出本机浏览器(不经过网关持久化)。 - 生产环境建议在网关层对
/swagger-ui.html、/swagger-debug.html、/v3/api-docs、/webjars/*做内网限制。 - Apifox/Postman 用户:导入
/v3/api-docs后需自行按 §4.1 实现签名前置脚本。
4. 鉴权与签名
4.1 算法
signature = Base64( HMAC-SHA256( appSecret, 签名原文 ) ) 签名原文 = timestamp + "\n" + rawBody
| 组成 | 说明 |
|---|---|
appSecret | 签名密钥(双向通用:我方推事件用它签名,贵方调指令用它签名) |
timestamp | 即请求头 X-Icaiy-Timestamp 的值 |
rawBody | HTTP 请求体原字符串;GET 等无 body 时为空串 "" |
4.2 计算步骤
以发送一条文本消息(请求体 {"clientCmdId":"c1","friendId":"78813033456789","msgType":"text","text":"你好"})为例:
- 取当前毫秒时间戳:
1789000000123。 - 签名原文 =
1789000000123\n{"clientCmdId":"c1","friendId":"78813033456789","msgType":"text","text":"你好"}(注意时间戳与 body 之间是换行符\n)。 - 用
appSecret对该原文做 HMAC-SHA256,再做 Base64 编码,即为X-Icaiy-Signature。
4.3 各语言示例
Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public static String sign(String appSecret, String timestamp, String rawBody) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
return Base64.getEncoder().encodeToString(
mac.doFinal((timestamp + "\n" + rawBody).getBytes(StandardCharsets.UTF_8)));
}Node.js
const crypto = require('crypto');
function sign(appSecret, timestamp, rawBody) {
return crypto.createHmac('sha256', appSecret)
.update(timestamp + '\n' + rawBody)
.digest('base64');
}Python
import base64, hashlib, hmac
def sign(app_secret: str, timestamp: str, raw_body: str) -> str:
message = (timestamp + '\n' + raw_body).encode('utf-8')
digest = hmac.new(app_secret.encode('utf-8'), message, hashlib.sha256).digest()
return base64.b64encode(digest).decode('utf-8')4.4 防重放说明
服务端校验签名后还会校验时间戳,偏差超过 ±5 分钟的请求直接拒绝(401);同一签名 5 分钟内仅受理一次(重复返回 401,更换时间戳重试即可)。请务必在请求时实时生成时间戳,不要复用缓存值。防重放仅对携带请求体的 POST 生效(GET / 空请求体请求不受影响)。
4.5 使用 curl 调用示例
# 发送文本消息
curl -X POST "http://39.102.117.196:9100/sdk/v2/robots/1688857577873824/messages" \
-H "Authorization: Bearer {appId}" \
-H "X-Icaiy-Timestamp: $(date +%s%3N)" \
-H "X-Icaiy-Signature: <按4.1计算>" \
-H "Content-Type: application/json" \
-d '{"clientCmdId":"test-001","friendId":"78813033456789","msgType":"text","text":"你好"}'date +%s%3N 在 GNU/Linux 下输出毫秒时间戳;macOS 自带 BSD date 不支持 %N(会输出字面量 N),请改用 $(date +%s)000 或 gdate +%s%3N(brew install coreutils)。5. 事件推送(我方 → 贵方)
本端为:我方在机器人产生数据/状态变化时,主动 POST 事件到贵方登记的 callbackUrl。
5.1 请求方式
POST {callbackUrl}
Content-Type: application/jsonGET /sdk/v2/debug/events 轮询可获得与推送内容完全一致的事件(见 §6.12.4),闭环验证不依赖本端点。5.2 请求头
| Header | 说明 |
|---|---|
X-Icaiy-Timestamp | 毫秒时间戳 |
X-Icaiy-Signature | 签名(算法见 第 4 章) |
X-Icaiy-Event | 事件类型(等同于 body 的 eventType,方便贵方快速分流) |
X-Icaiy-App-Id | 事件来源客户 appId |
5.3 通知内容(信封结构)
所有事件统一外层信封,业务数据放在 payload:
{
"specVersion": 1,
"eventId": "9f86a1c2-3d4e-4f5a-9b8c-1a2b3c4d5e6f",
"eventType": "message.received",
"occurredAt": 1760000000123,
"robot": { "wechatId": "1688857389" },
"payload": { }
}| 字段 | 类型 | 说明 |
|---|---|---|
| specVersion | int | 契约版本,当前恒为 1 |
| eventId | string | 事件唯一 ID(UUID),幂等去重键 |
| eventType | string | 事件类型,见 5.6 |
| occurredAt | long | 网关转发时间戳(毫秒) |
| robot.wechatId | string | 机器人企微账号 ID |
| payload | object | 事件业务数据,统一 schema(见 5.4) |
5.4 payload 统一格式(必读)
所有事件的 payload 由网关统一规范化,不透传设备端原始字段名。贵方只需实现一个事件解析函数即可覆盖全部类型。
- ① 字段命名:一律小驼峰
camelCase(如robotId/msgSvrId)。 - ② 类型规则:
| 类型 | JSON 表现 | 约定 |
|---|---|---|
| ID | string | robotId / peerId / groupId / msgId / msgSvrId 等一律字符串(企微 ID 超出 JS 安全整数,贵方无需做数字/字符串分支) |
| 数值 | number | type / total / timestamp / batteryPercent 等 |
| 布尔 | bool | success / selfSent / isOwner 等 |
| 枚举 | string | 设备端枚举名字符串,如 contentType:"Text"、gender:"Male"(值域见 §8.2 其他枚举) |
| 时间 | number(秒) | 统一字段名 timestamp(设备端口径);信封 occurredAt 是毫秒,为网关转发时间,两者语义不同 |
- ③ 三类 payload 骨架:
| 骨架 | 结构 | 适用事件 |
|---|---|---|
| 列表同步 | { robotId, ...定位字段, items:[...], total } | conversation.sync / contact.sync / group.sync / group.members / tag.list / history.batch |
| 实体通知 | { robotId, item:{...} }(结构同对应 items 元素) | contact.add / group.add / tag.add / tag.del |
| 结果/状态 | 平铺结果字段 | message.received / message.receipt / message.revoked / robot.online / robot.offline / account.updated / device.low_battery / task.result / group.changed / group.del / circle.list / circle.published |
- ④ 消息内容直读:
message.received/history.batch的content为 UTF-8 解码后的可读字符串(不再 Base64)——文本=原文、媒体=资源 URL、表情=EmotionInfo JSON 原文。 - ⑤ 可选字段省略:条件性字段(如群聊才有的
groupName)不出现在 JSON 中即为"本事件无此语义",按缺省处理,不要以空串/0 判断。 - ⑥ 完成判定:列表类
total是本批条数(=items.length),不是全量总数。设备端分批推送,同步完成判定靠静默期(收到最后一批后 30s 无新事件即认为完成)。
5.5 贵方应答与重试
贵方端点必须:
- 返回 HTTP 200 且 body 为
{"code":0}→ 视为投递成功。 - 其余任何情况(非 200、
code非 0、超时 10s)→ 视为失败。
// 贵方响应回调方(我方)的最小成功报文
{ "code": 0, "message": "ok" }失败重试:最多投递 5 次(首次 + 4 次重试),指数退避 2s / 4s / 8s / 16s,重试期间 eventId 不变。全部失败进入死信队列(保留 24h),贵方可经运维接口自助查看与重放(见 6.12.1)。
5.6 事件类型与数据结构
5.6.0 事件从哪来(触发机制总览,必读)
所有事件的数据源头都是机器人手机上的 APP:APP 内嵌企微(hook)感知企微内发生的一切,经加密长连接推到网关,网关翻译成标准事件后投递到贵方回调地址。按"谁触发、何时触发",共三条链路:
| 链路 | 触发方 | 触发时机 | 覆盖事件 |
|---|---|---|---|
| ① hook 实时上报 | 设备端自动(无需贵方做任何事) | 企微内动作发生的第一时间:收到/发出消息、对方撤回、好友添加成功、群变化、标签增删、设备上下线、电量低 | message.received、message.revoked(对方撤回)、contact.add、group.add/del/changed、tag.add/del、robot.online/offline、device.low_battery、forward.log |
| ② 指令触发回推 | 贵方调用指令 API 或 网关自动调度 | 贵方调对应端点(如 §6.2.3 sync / §6.3.3 拉历史 / §6.5.2 单群成员 / §6.4.4 单客户刷新)后,设备执行指令并把结果数据分批回推;group.members 全量续拉由网关在收到群列表后自动逐群进行(1群/秒),无需贵方参与 | conversation.sync、contact.sync、contact.updated、group.sync/members、tag.list、history.batch、emoji.list、circle.list/published、robot.qrcode、message.receipt、task.result、friends.detect.progress/result、voice.transcript、group.qrcode、logs.pulled |
| ③ 网关合成/转换 | 网关 | TCP 断开时网关合成 disconnected 离线事件;已在线机器人重复收到上线通知(资料刷新回推)时转为 account.updated | robot.offline(disconnected)、account.updated |
robot.online 事件作为向该机器人发指令的可靠信号——TCP 已连但企微尚未登录完成时(robot.online 未推送前),指令会被设备端静默丢弃(无 task.result、无任何报错)。2008 机器人不在线 仅覆盖 TCP 未连接的场景,防不住"在线但未登录完成"。数据同步指令与回推事件的对应关系(POST /robots/{wechatId}/sync 的 scope → 事件,详见 §6.2.3):
| scope | 网关下发给 APP 的动作 | 回推事件(设备端固定编排,按序回推) |
|---|---|---|
groups | 触发群列表推送(1210,flag=1)→ APP 端执行全量同步编排:① 群列表上报 → ② 约 5s 后会话分页 → ③ 客户+内部联系人+潜在客户上报 → ④ 账号资料刷新 → ⑤ 两通道静默后回发 syncComplete 收口标记 | group.sync(仅本账号归属的客户群)→ 网关自动续拉 → group.members(1群/秒)+ conversation.sync(约 5s 后开始,每 5 条一批)+ contact.sync(客户批每 20 条双发 friends+contacts,含潜在客户;内部联系人每 100 条)+ account.updated;收尾有一条 id 为空的占位 group.sync,入库时丢弃 |
contacts | 拉取全部联系人(1212,contact 留空=全量)→ APP 端编排:客户+内部联系人+潜在客户上报 → 账号资料刷新 | contact.sync(客户批每 20 条双发 + 内部联系人每 100 条)+ account.updated |
tags | 拉取标签全量(1287) | tag.list(单事件一次到齐) |
account | 拉自身资料并刷新(29) | account.updated(资料字段同 robot.online) |
groups 是全量同步全家桶——只传 ["groups"] 也会收到群+成员+会话+客户+账号全部事件。需要"只要群"请在接收端按 eventType 自行过滤,不要指望 scope 帮你过滤。同一机器人两次同步请间隔 ≥60s(设备端客户/会话同步各有 60s 去重窗口,窗口内第二发会被设备静默跳过)。groups scope 全家桶带回 + hook 实时增量送达;好友资料由 contacts scope 带回。conversation/friends 不是有效 scope 值(可选值仅 groups/contacts/tags/account)。两类来源容易混淆的点:
conversation.sync/contact.sync/group.sync 事件名带 "sync" 但不只在全量同步时出现——hook 检测到会话/群实时变化时也会推同名事件(增量,通常单条);而 tag.add/contact.add 等 "add" 事件只在企微内实际发生动作时实时推送,同步指令不会触发它们。5.6.1 事件类型总览
| eventType | 说明 | 触发方式(触发链路见 5.6.0) |
|---|---|---|
robot.online / robot.offline | 机器人上下线(状态翻转时投递,offline 带结构化 reason) | ① APP 连上网关 TCP 时推上线;断开/登出/封号时推离线,TCP 异常断开由 ③ 网关合成 |
account.updated | 已在线机器人账号资料刷新 | ② 调 sync scopes=["account"](§6.2.3)→ APP 刷新自身资料后回推 |
device.low_battery | 低电量预警(设备仍在线) | ① 设备电量低时主动上报 |
message.received | 收到/发出聊天消息(含机器人回显) | ① hook 实时:企微收/发消息即刻上报;② 调订阅开关(§6.7.2)后按订阅范围回推 |
message.receipt | 发消息异步执行回执 | ② 调发消息指令(§6.3.1)后 APP 执行完毕回执 |
message.revoked | 消息被撤回 | 双源:① 对方撤回 hook 实时检测;② 己方调撤回指令(§6.3.2)成功回推 |
conversation.sync | 会话列表 / 会话变更 | 三源:② 调 sync scopes=["groups"](§6.2.3,groups 全家桶编排,群列表后约 5s 开始)全量回推(每 5 条一批);机器人登录成功后设备自动回推一次(无需调用);① hook 实时增量(会话新增/变更,通常单条) |
contact.sync | 客户/联系人列表(分批量) | ② 调 sync scopes=["contacts"] 或 ["groups"](§6.2.3,两种编排都会回推客户数据)→ 全量回推(客户批每 20 条 / 内部每 100 条)。资料变更无自动实时事件,感知走三层:单聊快照脏检测 → §6.4.4 按需刷新 → 定期全量对账(见 §5.6.8 感知策略) |
contact.add / contact.updated | 新好友实时通知 / 单客户资料刷新回推 | contact.add:① hook 实时(企微内好友添加成功,含贵方通过好友申请 §6.4.1 之后);contact.updated:② 调 §6.4.4 刷新单个客户资料 后回推 |
group.sync | 客户群列表 | 双源:② 调 sync scopes=["groups"] 全量回推;① hook 实时(进群/群资料变化) |
group.members | 群成员明细(每群一个事件) | 双源:② 网关自动调度(收到群列表后 1群/秒 逐群拉取,无需贵方调用);② 贵方调 §6.5.2 拉取指定群成员 单群刷新 |
group.add / group.del / group.changed | 群生命周期 | ① hook 实时:进群/退群/群资料变更 |
tag.list | 标签全量列表 | ② 调 sync scopes=["tags"](§6.2.3),单事件一次到齐 |
tag.add / tag.del | 标签新增/删除 | ① hook 实时:企微内标签新增/删除 |
emoji.list | 收藏表情列表 | ② 调 emoji/fetch(§6.8.1) |
circle.list | 朋友圈列表 | ② 调朋友圈拉取(§6.6.6,可拉自己的/指定好友的)触发 APP 回推 |
circle.published | 发布朋友圈结果 | ② 调发布朋友圈指令(§6.6.1)后回推 |
task.result | 指令执行任务回执 | ② 有结果语义的指令执行完回推(改备注/删好友/加表情/打标签/改资料/标签管理等,见 §6.13 矩阵) |
robot.qrcode | 机器人个人二维码(图片 URL) | ② 调 qrcode(§6.2.5):APP 拉码 → 传 OSS → 回推 URL |
history.batch | 历史消息批次 | ② 调拉历史指令(§6.3.3),按 count 分批回推 |
friends.detect.progress / friends.detect.result | 僵尸粉检测过程 / 终态(均含 taskId 供关联) | ② 调僵尸粉检测(§6.4.9)后回推 |
voice.transcript | 语音转文字结果 | ② 调语音转文字(§6.3.4)后回推 |
group.qrcode | 群二维码(图片 URL) | ② 调群二维码(§6.5.3)后回推 |
logs.pulled | APP 日志上传完成(fileUrl=日志下载地址) | ② 调拉取 APP 日志(§6.12.3)后回推 |
forward.log | 发单日志(发单/转发消息发送明细,含成功/失败状态) | ① 设备执行发单/转发后回推明细 |
eventType(返回 success 即可)。5.6.2 message.received — 聊天消息
机器人收到或发出的每一条聊天消息(含机器人自己发出的回显)。
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| peerId | string | 会话对方 ID(单聊=人 ID,群聊=群 ID,无 R: 前缀) |
| msgId | string | 企微本地消息 ID |
| msgSvrId | string | 企微服务端消息 ID(撤回关联键,务必存储) |
| senderId | string | 发送者 ID |
| selfSent | bool | 是否机器人自己发出(=senderId 为机器人) |
| contentType | string | 内容类型枚举名,如 "Text"(见 §8.1 消息内容类型) |
| content | string | 消息内容(UTF-8 直读):文本=原文、媒体=资源 URL、表情=EmotionInfo JSON |
| timestamp | number(秒) | 消息时间 |
| name | string | 会话对方昵称 |
| avatar | string | 会话对方头像 |
| talkerId | string | 群消息实际发言者 ID(仅群聊携带) |
| groupName / groupAvatar | string | 群名 / 群头像(仅群聊携带) |
| corpName | string | 对端企业名称(有则带) |
| ffAccount / ffNick / customFriendId / userInfo / ext | string | 设备端扩展透传字段(有值才携带;不需要可直接忽略) |
| atMe | bool | 群聊中机器人是否被 @(设备端 hook 结构化解析,被 @成员 / @所有人 均置位)。@其他成员无结构化上报,仅能从 content 文本中的 @昵称 字样辨认 |
| customExt | string | 引用消息元数据 JSON(引用消息必读,勿忽略):{"quote":1,"refContent":"被引用原文摘要(≤100字)","name":"被引用人昵称","convId":"会话ID","time":被引用时间,"uin","appinfo","ct"}。识别引用消息以 quote=1 为准,勿依赖 contentType;非引用消息不携带本字段 |
| disturbEnable | bool | 对端会话是否免打扰 |
示例:
{
"specVersion": 1, "eventId": "9f86a1c2-3d4e-4f5a-9b8c-1a2b3c4d5e6f", "eventType": "message.received",
"occurredAt": 1760000000123, "robot": {"wechatId": "1688857389"},
"payload": {
"robotId": "1688857389", "peerId": "78813033456789",
"msgId": "1028456", "msgSvrId": "8021934567890123", "senderId": "78813033456789",
"selfSent": false, "atMe": false, "contentType": "Text", "content": "你好,请问有货吗",
"timestamp": 1760000000, "name": "张三",
"avatar": "https://wx.qlogo.cn/xxx", "disturbEnable": false
}
}引用消息示例(群聊中被引用的消息,customExt.quote=1 识别):
{
"specVersion": 1, "eventType": "message.received",
"payload": {
"robotId": "1688857389", "peerId": "WRaaaa@im.chatroom", "senderId": "7881300998xxx",
"atMe": false, "contentType": "Text", "content": "我同意这个方案",
"customExt": "{\"convType\":1,\"quote\":1,\"refContent\":\"明天上午开会请准时\",\"name\":\"张三\",\"convId\":\"...\",\"time\":1760000000}"
}
}selfSent=true)。贵方发出消息后以 message.receipt 为准更新状态,收到自己消息的 received 事件按 clientCmdId/时间窗去重。被过滤策略命中的消息不会产生本事件:设备端按 §6.10.3
/device-config 下发的 msgFilter(停止接待 / 屏蔽对象 / 屏蔽关键词)在上报前直接丢弃,贵方不会收到、也无需去重;§6.3.3 主动拉取的历史消息不受该策略影响。5.6.3 message.receipt — 发消息回执
贵方调用发消息指令后的异步执行结果。
| 字段 | 类型 | 说明 |
|---|---|---|
| clientCmdId | string | 贵方指令关联 ID(网关回填,与请求一致) |
| success | bool | 是否发送成功 |
| errorCode | string | 结果码:Success / NoRight / InvalidParam / InternalError / TargetNotOnline |
| errorMessage | string | 失败原因(设备端原文) |
| robotId | string | 机器人企微 ID |
| peerId | string | 接收方 ID |
| msgId | string | 企微本地消息 ID(撤回该消息用此值) |
| msgSvrId | string | 企微服务端消息 ID(入库备查) |
| timestamp | number(秒) | 时间 |
成功示例:
{
"specVersion": 1, "eventId": "b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e", "eventType": "message.receipt",
"occurredAt": 1760000002000, "robot": {"wechatId": "1688857389"},
"payload": {
"success": true, "errorCode": "Success", "robotId": "1688857389",
"peerId": "78813033456789", "msgId": "494360", "msgSvrId": "8021934567890123",
"clientCmdId": "your-uuid-0001", "timestamp": 1760000000
}
}失败示例:
{
"specVersion": 1, "eventId": "c2d4e5f6-7a8b-9c0d-1e2f-3a4b5c6d7e8f", "eventType": "message.receipt",
"occurredAt": 1760000003000, "robot": {"wechatId": "1688857389"},
"payload": {
"success": false, "errorCode": "InternalError",
"errorMessage": "发送失败: 不是好友关系", "robotId": "1688857389",
"peerId": "78813033456789", "msgId": "494361", "clientCmdId": "your-uuid-0002",
"timestamp": 1760000001
}
}5.6.4 message.revoked — 消息撤回
消息被撤回时推送,两种触发:①贵方撤回指令(§6.3.2)执行成功;②设备 hook 检测到对方撤回。
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| peerId | string | 会话 ID(单聊=人 ID,群聊=群 ID) |
| msgSvrId | string | 被撤回消息的企微服务端 ID(关联 message.received 的 msgSvrId) |
| operatorId | string | 撤回操作者 ID(机器人自己撤回时=robotId) |
| timestamp | number(秒) | 撤回时间 |
| originalContent | string | 被撤回消息原文(设备端能取到时返回) |
示例:
{
"specVersion": 1, "eventId": "r7v1a2b3-c4d5-e6f7-a8b9-c0d1e2f3a4b5", "eventType": "message.revoked",
"occurredAt": 1760000010000, "robot": {"wechatId": "1688857389"},
"payload": {
"robotId": "1688857389", "peerId": "78813033456789",
"msgSvrId": "8021934567890123", "operatorId": "1688857389",
"timestamp": 1760000009, "originalContent": "这条消息撤回了"
}
}5.6.5 robot.online / robot.offline — 上下线
robot.online。因此贵方重启/重新部署后必须先调 GET /robots 快照重建在线初始视图,再靠事件增量维护。robot.online payload:
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| wechatNo | string | 企微号别名(可修改的对外号,通过 §6.2.4 wechatNo 参数变更;改完以最新 account.updated 事件为准) |
| name | string | 企微昵称(通过 §6.2.4 nickName 参数变更) |
| gender | string | 恒为 "Male"(设备端硬编码) |
| avatar | string | 头像 URL |
| imei | string | 设备 IMEI |
| phone | string | 账号绑定手机号(注册手机,账号固有属性)。注意:不是对外显示的号,改资料接口(§6.2.4)改不了它——企微换绑手机需短信验证,协议层无此能力。若本账号的企微号别名恰好设为手机号样式,本字段与 wechatNo 会同值,但两者是不同字段、变更互不影响 |
| wxVersion | string | 企微客户端版本 |
| clientVersion | string | APP 客户端版本 |
| serverAddr | string | APP 服务端接入地址(非网络 IP) |
| companyId / companyName | string | 企业 ID / 名称 |
| externalNameType | number | 对外显示名类型:0=未知 1=实名 2=别名。别人看到机器人的"号"由它决定:1 显示 name(实名),2 显示 wechatNo(别名)——只改别名文本不切 externalNameType=2,对方看到的不会变 |
externalNameType 对号入座:想改"对外显示的号"→ §6.2.4 传 wechatNo + externalNameType=2,生效后看 account.updated 的 wechatNo 字段;phone 字段是注册绑定手机,任何资料修改都不动它。修改成功后 APP 会上报两波 account.updated(先参数直写、后服务器权威值),以最后一波为准;同时 task.result(taskType=ModifyProfileTask)有明确成功/失败回执,失败原因(如企微拒绝)以它为准。robot.offline payload:
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| imei | string | 设备 IMEI |
| desc | string | 设备端原文 |
| reason | string | 结构化分类,见下表 |
| reason | 对应场景 | 贵方建议动作 |
|---|---|---|
banned | 封号(desc 含"hook检测到封号"/"账号被冻结") | 停止该账号全部自动业务,人工处理 |
logout | IM 主动登出(企微账号在设备退出登录) | 标记离线,等待重新登录 |
forced_logout | 强制下线(人脸识别/登录异常等) | 标记离线,人工确认后重登 |
shutdown | 设备关机 | 标记离线,运维处理 |
disconnected | TCP 异常断开(网络波动/APP被杀/平台踢线),网关合成 | 标记离线,APP 自动重连 |
offline 示例:
{
"specVersion": 1, "eventId": "d3e5f6a7-b8c9-d0e1-f2a3-b4c5d6e7f8a9", "eventType": "robot.offline",
"occurredAt": 1760000004000, "robot": {"wechatId": "1688857389"},
"payload": {
"robotId": "1688857389", "imei": "866897051234567",
"desc": "hook检测到封号,禁止自动收发消息", "reason": "banned"
}
}5.6.6 conversation.sync — 会话列表 / 会话变更
会话(单聊+群聊)数据。三源:groups scope 全家桶编排全量回推(每 5 条一批)+ 机器人登录成功后设备自动回推一次 + hook 实时增量(会话新增/变更,通常单条)。
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| items | array | 会话元素数组 |
| items[].id | string | 会话 ID(单聊=对方 ID,群聊=群 ID,无 R: 前缀) |
| items[].name | string | 会话名 |
| items[].avatar | string | 会话头像 |
| items[].type | number | 0=单聊 1=群聊 |
| items[].ownerId | string | 群主 ID(群聊且有值才携带) |
| items[].notice | string | 群公告(有值才携带) |
| items[].memberCount | number | 群成员数(>0 才携带) |
| total | number | 本批条数(非全量总数,完成判定靠静默期) |
group.sync 仅回推客户群(外部群),企业内部群请从本事件(type=1)筛选。5.6.7 contact.sync — 客户 / 联系人列表(分批)
客户/联系人数据分批回推。source 标识载荷通道,两通道元素结构不同:
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| source | string | friends=客户/微信好友(2026 通道);contacts=企微联系人(2037 通道,含客户批+内部联系人批) |
| items | array | 元素数组(结构见下) |
| total | number | 本批条数 |
source=friends 元素(客户/微信好友,contact.add/contact.updated 的 item 同此结构):
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 客户 ID |
| no | string | 客户微信号(有值才携带) |
| name | string | 昵称 |
| memo | string | 备注名(企微"备注名"字段,有值才携带) |
| gender | string | 性别枚举名 |
| avatar | string | 头像(有值才携带) |
| remark | string | 备注(有值才携带) |
| contactType | number | 1=企业内部联系人 / 2=微信客户(ID 788 前缀)/ 3=企微外部客户 |
| tagIds | string | 标签 ID(逗号分隔,有值才携带) |
| phone | string | 手机号(有值才携带) |
| description | string | 描述(有值才携带) |
| addSource | string | 添加来源 |
| addTime | number | 添加时间戳(秒,>0 才携带) |
| openId | string | 开放平台 ID(有值才携带) |
source=contacts 元素(企微联系人):
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 联系人 ID |
| alias | string | 别名(企微号) |
| name | string | 昵称 |
| avatar | string | 头像 |
| phone | string | 手机号(有值才携带) |
| description | string | 描述(有值才携带) |
| company | string | 企业名(内部联系人批此字段为纯数字 acctid) |
| contactType | number | 788 前缀→2;company 纯数字→1;其余→3 |
示例(friends 批,1 条):
{
"specVersion": 1, "eventId": "f1a2b3c4-d5e6-f7a8-b9c0-d1e2f3a4b5c6", "eventType": "contact.sync",
"occurredAt": 1760000010000, "robot": {"wechatId": "1688857389"},
"payload": {
"robotId": "1688857389", "source": "friends",
"items": [{
"id": "7881300998xxx", "no": "kehu_wx001", "name": "张三",
"gender": "Male", "contactType": 2, "tagIds": "101,102",
"addSource": "搜索", "addTime": 1759999000
}],
"total": 1
}
}5.6.8 contact.add / contact.updated — 新好友与资料刷新
| eventType | 触发 | payload |
|---|---|---|
contact.add | ① hook 实时:企微内好友添加成功(含贵方通过好友申请 §6.4.1 之后) | robotId + item(单条,结构同 §5.6.7 source=friends 元素) |
contact.updated | ② 调 §6.4.4 刷新单个客户资料 后回推 | 同上 |
contact.sync 只在同步与脏检测时回推。推荐三层感知:① 单聊消息快照对比发现不一致 → ② 调 §6.4.4 按需刷新单客户(contact.updated 即时回推)→ ③ 定期调 sync scopes=["contacts"](§6.2.3)全量对账。5.6.9 group.sync / group.members — 群列表与群成员
group.sync:客户群列表(仅客户群;单页全量)。双源:② sync scopes=["groups"] 全量回推;① hook 实时(进群/群资料变化)。
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| items | array | 群元素:id/name/avatar/ownerId(有值才携带)/memberCount(>0 才携带) |
| total | number | 本批条数 |
groups scope 编排的收尾会有一条 id 为空的占位 group.sync(同步完成标记),入库时请丢弃。group.members:群成员明细,每群一个事件。双源:② 网关自动调度(收到群列表后自动逐群拉取,1群/秒,无需贵方参与);② 贵方调 §6.5.2 拉取指定群成员 单群刷新。
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| groupId | string | 群 ID |
| items | array | 成员元素(见下) |
| items[].id | string | 成员 ID |
| items[].name | string | 成员昵称(群昵称) |
| items[].avatar | string | 头像 |
| items[].type | number | 设备端成员类型 |
| items[].isOwner | bool | 是否群主 |
| items[].isAdmin | bool | 是否群管理员 |
| items[].joinTime | number | 入群时间戳(秒) |
| total | number | 本群成员数 |
示例:
{
"specVersion": 1, "eventId": "g9c1d2e3-f4a5-b6c7-d8e9-f0a1b2c3d4e5", "eventType": "group.members",
"occurredAt": 1760000020000, "robot": {"wechatId": "1688857389"},
"payload": {
"robotId": "1688857389", "groupId": "WRaaaaaaa@im.chatroom",
"items": [
{"id": "1688857389", "name": "机器人小助手", "avatar": "https://...", "type": 1, "isOwner": true, "isAdmin": false, "joinTime": 0},
{"id": "7881300998xxx", "name": "张三", "avatar": "https://...", "type": 0, "isOwner": false, "isAdmin": true, "joinTime": 1750000000}
],
"total": 2
}
}5.6.10 group.add / group.del / group.changed — 群生命周期
| eventType | 触发 | payload |
|---|---|---|
group.add | ① hook 实时:新群产生(含建群指令 §6.5.1 action=create 成功后) | robotId + item(单群,结构同 §5.6.9 group.sync 元素) |
group.del | ① hook 实时:退群/被移出群 | robotId + groupId |
group.changed | ① hook 实时:群资料变更 | 见下表 |
group.changed:
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| groupId | string | 群 ID |
| change | string | 变更类型(设备端枚举名,如改群名/公告变更/成员列表变化 Change_MemberList) |
| content | string | 变更内容(新群名/新公告文本) |
| noticeSender | object | 公告发布者(仅公告变更携带):id/name/avatar |
| noticeTime | number | 公告发布时间戳(秒,仅公告变更携带) |
5.6.11 tag.list / tag.add / tag.del — 标签
| eventType | 触发 | payload |
|---|---|---|
tag.list | ② 调 sync scopes=["tags"](§6.2.3)或标签同步指令触发 | robotId + items(全量标签,单事件一次到齐)+ total |
tag.add | ① hook 实时:企微内标签新增(含 §6.9.1 新建标签成功后) | robotId + item(完整标签) |
tag.del | ① hook 实时:企微内标签删除(含 §6.9.2 删除标签成功后) | robotId + item(仅 id) |
标签元素(tag.list items / tag.add item 同构):
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 标签 ID(标签管理的删除/打标签都以它为准) |
| name | string | 标签名 |
| groupId | string | 标签组 ID |
| type / dataType / sortOrder | number | 设备端口径字段 |
5.6.12 history.batch — 历史消息批次
调 §6.3.3 拉取历史消息 后,设备按 count 分批回推。
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| items | array | 消息元素:peerId/contentType(枚举名)/content(UTF-8 直读)/msgId/msgSvrId/selfSent(bool,是否机器人发出)/timestamp(秒)/status(设备端消息状态) |
| total | number | 本批条数 |
| page | number | 页码 |
5.6.13 emoji.list / circle.list / circle.published — 表情与朋友圈数据
emoji.list:调 §6.8.1 拉取收藏表情 后回推。items 元素与发消息(§6.3.1)的 emotionInfo 对象同构——拿到列表后原样回传即可发送同款表情。
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| taskId | string | 关联任务 ID |
| items | array | 表情元素:url(CDN 直链)/staticUrl(缩略图)/md5/aesKey/desc/groupId/type/width/height/size |
| total | number | 本批条数 |
circle.list:调 §6.6.6 拉取朋友圈 后回推(拉自己的/指定好友的都经此通道)。
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| items | array | 朋友圈元素(见下) |
| items[].id | string | 朋友圈 ID |
| items[].publishTime | number | 发布时间戳(秒) |
| items[].text | string | 文字内容(有才携带) |
| items[].images | array | 图片列表:url/thumb/mediaId/title |
| items[].link | object | 链接附件:同上结构 |
| items[].video | object | 视频附件:同上结构 |
| items[].likes | array | 点赞:friendId/name(有才携带)/timestamp |
| items[].comments | array | 评论:id/content/fromId/fromName/toId/toName/replyCommentId(回复评论时有)/timestamp |
| total / page | number | 本批条数 / 页码 |
circle.published:§6.6.1 发布朋友圈 的最终成败回执(taskId 关联 + clientCmdId 回填)。
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| taskId | string | 任务 ID |
| success | bool | 发布结果 |
| errorCode / errorMessage | string | 失败码 / 失败原因(成功时为设备端默认值) |
| circleId | string | 新朋友圈 ID(成功时携带) |
5.6.14 task.result / account.updated / device.low_battery
task.result:通用任务回执——有结果语义指令的最终成败通道(哪些指令走此通道见 §6.13 回执矩阵)。
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| taskId | string | 任务 ID |
| taskType | string | 设备端任务类型枚举名(如 AcceptFriendAddRequestTask、AddFriendWithSceneTask、ChatRoomInviteApproveTask、ContactLabelTask 等) |
| success | bool | 执行结果 |
| errorCode / errorMessage | string | 失败码 / 失败原因 |
| clientCmdId | string | 贵方指令 ID(网关按 taskId 关联回填;下发时已登记) |
| data | string | 任务附数据(部分任务携带,如搜索加好友的候选列表 JSON) |
account.updated:已在线机器人账号资料刷新回推(sync scopes=["account"]/groups 编排触发)。payload 同 §5.6.5 robot.online。
device.low_battery:低电量预警(设备仍在线,不触发 robot.offline)。
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| imei | string | 设备 IMEI |
| desc | string | 设备端原文(low_battery:{百分比}) |
| batteryPercent | number | 电量百分比(网关解析) |
5.6.15 forward.log — 发单日志
设备执行发单/转发后回推发送明细(含成功/失败状态与转链结果),每条 item = 一次转发发送记录。
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| mobile | string | 设备手机号(有值才携带) |
| items | array | 明细元素(见下) |
| total | number | 本批条数 |
items 元素:
| 字段 | 类型 | 说明 |
|---|---|---|
| shillPlanSourceId / shillPlanTargetId | number | 发单计划源 / 目标 ID |
| sourceRoomId | string | 采集群 ID(有值才携带) |
| collWechatId | string | 采集成员 ID(有值才携带) |
| sourceWechatId | string | 源机器人 ID(有值才携带) |
| senderWechatId | string | 执行发送的机器人 ID(有值才携带) |
| targetRoomId | string | 目标群 ID(有值才携带) |
| forwardMessageId | string | 被转发消息 ID |
| forwardNewMessageId | string | 转发后的新消息 ID(有值才携带) |
| sendStatus | string | 发送状态(成功/失败) |
| sendTime / preSendTime | string | 实际发送时间 / 预定发送时间(有值才携带) |
| sendMemo | string | 备注(失败原因等,有值才携带) |
| contentType / content / originalContent | string | 内容类型 / 内容 / 原始内容(有值才携带) |
| convertStatus | number | 转链状态 |
| convertFailReason | string | 转链失败原因(有值才携带) |
5.6.16 robot.qrcode — 机器人个人二维码
调 §6.2.5 获取机器人二维码 后回推:APP 拉码 → 上传 → 回推图片 URL。
| 字段 | 类型 | 说明 |
|---|---|---|
| robotId | string | 机器人企微 ID |
| success | bool | 拉码结果 |
| qrcodeUrl | string | 二维码图片 URL(成功时携带) |
| errorMsg | string | 失败原因(失败时携带) |
| clientCmdId | string | 贵方指令 ID(网关按机器人维度 FIFO 关联回填) |
5.7 媒体上传与文件 URL(媒体文件从哪来)
机器人产生的媒体资源(聊天图片/语音/视频/文件、二维码图片、APP 日志等)的 URL 从哪来?由贵方凭证的 mediaUploadUrl 配置决定,共两条路径:
路径 A:贵方自带存储(mediaUploadUrl 已配置 = 中转模式,推荐)
APP 下载企微媒体 → POST {网关}/fileUpload(multipart:folder + file,头 X-Access-Token)
→ 网关鉴权(token 须为当前在线机器人)→ 流式中转 → 贵方存储端点 → 返回 url → 回传 APP
→ 后续事件推送中的媒体 content 字段即该 URL选择此路径,贵方需实现一个存储接收端点,契约如下:
| 项 | 契约 |
|---|---|
| 请求方法与地址 | POST {mediaUploadUrl}?folder={目录}&filename={文件名}&wechatId={机器人ID}(folder 为资源目录,由 APP 决定,如二维码/媒体/日志) |
| 请求体 | 原始文件字节(application/octet-stream,非 multipart) |
| 请求头 | X-Icaiy-App-Id、X-Icaiy-Timestamp、X-Icaiy-Signature——签名 = Base64(HMAC-SHA256(appSecret, timestamp + "\n" + 文件原始字节)),与 §4 签名同算法,被签内容是文件字节流 |
| 应答要求 | HTTP 200 且 body 为 {"code": 0, "data": {"url": "https://..."}}——url 即最终可访问的文件地址,将原样出现在后续事件推送的媒体字段中 |
网关在此链路是纯管道:不存储、不转码、不解析,字节全程流式过境(两遍流转签名,全量字节不落堆,支持大视频并发);文件大小上限默认 100MB(网关配置 media_upload.max-file-size-mb)。
路径 B:我方托管 OSS(mediaUploadUrl 未配置)
该客户 APP 的上传地址指向我方控制面 OSS(单独计费模式),事件中的 URL 即我方 OSS 地址(链接有有效期,贵方应及时转存自己的存储)。
秒传预检:APP 上传前会先调 POST /fileUploadMd5Check(MD5 秒传检查)。网关无去重存储,恒返回未命中(hit=false),APP 自动降级为正常上传——贵方无需关心该端点。
/fileUpload、/fileUploadMd5Check 属于设备内部链路(鉴权用机器人 TCP 认证 token X-Access-Token,非贵方 appSecret 体系),贵方无需直接调用;仅当选择路径 A 时需要实现存储接收端点。6. 指令 API(贵方 → 我方,/sdk/v2)
本章是贵方驱动机器人的全部指令接口,共 45 个端点:43 个 /sdk/v2 业务端点(按功能域分为 10 组;总览表中 6.10.2 为贵方实现的转链契约条目,非网关端点)+ 2 个媒体上传内部端点(§6.11,贵方通常无需直接调用)。全部端点共用 §6.1 的通用约定(三道闸校验、幂等、回执模型),各端点章节只描述差异部分。
code 为准(§3.3);指令均为异步执行——code=200(受理成功)≠ 执行成功,最终结果按各端点声明的 receiptEvent 事件回推(§5.6);幂等与回执模型见 §3.4。6.1 通用约定
6.1.1 统一处理链路与通用错误码
每个端点按以下顺序校验,任一步失败即短路返回(错误响应统一无 data 字段,仅 {code, message}):
| 步骤 | 校验 | 失败码 | message 示例 |
|---|---|---|---|
| ① 鉴权 | Bearer appId + 时间戳 + 签名(§4) | 401 | appId无效或客户已停用(Authorization: Bearer {appId}) / X-Icaiy-Timestamp 缺失或非法 / 时间戳超出±5分钟有效窗 / 签名校验失败 / 重复请求签名(疑似重放), 请更换时间戳重试 |
| ② 限流 | 客户级 cmd_qps_limit(次/秒,Redis 固定窗口) | 429 | 指令限流(cmd_qps_limit=10/秒), 请降低调用频率 |
| ③ 归属 | URL 中 wechatId 必须属于该 appId | 2004 | 机器人不属于该客户 |
| ④ 参数 | 字段校验(错误信息具体到字段) | 400 | 如 friendId 必填 |
| ⑤ 幂等 | clientCmdId 重复检测(Redis SETNX 24h) | 409 | clientCmdId 重复(幂等拦截): {clientCmdId} |
| ⑥ 在线 | 机器人 TCP 是否在线 | 2008 | 机器人不在线 |
| ⑦ 应答 | 查询型端点等设备回传(§6.10.4) | 504 | 设备应答超时(5s): … |
其余错误:500 网关内部错误。幂等位自动释放:指令受理后若 TCP 下发失败(2008),网关自动释放本次占用的幂等键,贵方修复后可用同一 clientCmdId 重发。
6.1.2 clientCmdId 与幂等(两档)
贵方自定义唯一指令 ID(≤64 字符建议),贯穿「请求受理 → 回执事件回填 → data 回显」全生命周期(§3.4)。幂等分两档,每端点章节均已标注:
| 档位 | 语义 | 缺失时 |
|---|---|---|
| 必填档 | 回执需 taskId/clientCmdId 精确关联的指令(发消息/群操作/朋友圈操作/改资料/加表情等) | 400:clientCmdId 必填(幂等键 + 回执关联键) |
| 可选档 | 受理即走的能力端点(语音转文字/僵尸粉检测与重加/群二维码/标签新建/表情拉取/拉日志) | 不传直接执行,传入则幂等 |
6.1.3 回执模型(receiptEvent / receiptMode)
指令端点成功响应的 data 中包含回执声明字段:
| 字段 | 说明 |
|---|---|
taskId | 网关任务 ID(回执事件中回传,用于关联;部分端点无) |
receiptEvent | 后续回执/结果事件名;none 态不返回此字段 |
receiptMode | task=taskId 精确关联(task.result/message.receipt 事件回传 taskId+clientCmdId);event=结果经事件流推送感知(事件携带 taskId 供关联);none=仅下发受理、无任务级回执 |
clientCmdId | 请求携带时原样回显 |
查询型端点(无回执语义)不返回回执字段。
6.1.4 端点总览(45 个)
| # | 端点 | 方法 | 用途 | 幂等 | 回执(receiptEvent / mode) |
|---|---|---|---|---|---|
| 6.2.1 | /robots | GET | 机器人列表快照 | — | —(查询) |
| 6.2.2 | /robots/{wechatId}/status | GET | 查询在线状态 | — | —(查询) |
| 6.2.3 | /robots/{wechatId}/sync | POST | 触发数据同步 | 天然幂等 | —(数据经事件流送达) |
| 6.2.4 | /robots/{wechatId}/profile | POST | 修改机器人资料 | 必填 | task.result / task |
| 6.2.5 | /robots/{wechatId}/qrcode | POST | 获取机器人二维码 | 必填 | robot.qrcode / event |
| 6.3.1 | /robots/{wechatId}/messages | POST | 发送消息 | 必填 | message.receipt / task |
| 6.3.2 | /robots/{wechatId}/messages/revoke | POST | 撤回消息 | 必填 | message.revoked / event |
| 6.3.3 | /robots/{wechatId}/messages/history | POST | 拉取历史消息 | 必填 | history.batch / event |
| 6.3.4 | /robots/{wechatId}/messages/voice-to-text | POST | 语音转文字 | 可选 | voice.transcript / event |
| 6.4.1 | /robots/{wechatId}/friends/accept | POST | 通过/拒绝好友申请 | 必填 | task.result + contact.add / task |
| 6.4.2 | /robots/{wechatId}/friends/remark | POST | 修改好友备注 | 必填 | task.result / task |
| 6.4.3 | /robots/{wechatId}/friends/delete | POST | 删除好友 | 必填 | task.result / task |
| 6.4.4 | /robots/{wechatId}/contacts/{contactId}/refresh | POST | 刷新单个客户资料 | 必填 | contact.updated / event |
| 6.4.5 | /robots/{wechatId}/friends/labels | POST | 给客户打标签 | 必填 | task.result / task |
| 6.4.6 | /robots/{wechatId}/friends/add | POST | 批量主动加好友 | 必填 | task.result(批次级)+ contact.add / task |
| 6.4.7 | /robots/{wechatId}/friends/search | POST | 搜索账号候选(只搜不加) | 必填 | task.result / task |
| 6.4.8 | /robots/{wechatId}/friends/search-add | POST | 搜索并直接加好友 | 必填 | task.result / task |
| 6.4.9 | /robots/{wechatId}/friends/detect | POST | 僵尸粉检测 | 可选 | friends.detect.result / event |
| 6.4.10 | /robots/{wechatId}/friends/readd | POST | 僵尸粉重加 | 可选 | task.result / task |
| 6.5.1 | /robots/{wechatId}/group-actions | POST | 群操作(建群/改名/公告/踢人/退群/转让/邀请) | 必填 | task.result(除 create)+ group.add(create)/group.changed / task |
| 6.5.2 | /robots/{wechatId}/groups/{groupId}/members | POST | 拉取指定群成员 | 必填 | group.members / event |
| 6.5.3 | /robots/{wechatId}/groups/qrcode | POST | 获取群二维码 | 可选 | group.qrcode / event |
| 6.5.4 | /robots/{wechatId}/group-invites/approve | POST | 群邀请审批 | 可选 | task.result / task |
| 6.5.5 | /robots/{wechatId}/groups/join-by-qr | POST | 扫码进群 | 可选 | task.result / task |
| 6.6.1 | /robots/{wechatId}/circles | POST | 发布朋友圈 | 必填 | circle.published / event(带 clientCmdId) |
| 6.6.2 | /robots/{wechatId}/circles/delete | POST | 删除朋友圈 | 必填 | task.result / task |
| 6.6.3 | /robots/{wechatId}/circles/like | POST | 朋友圈点赞 | 必填 | task.result / task |
| 6.6.4 | /robots/{wechatId}/circles/comment | POST | 朋友圈评论 | 必填 | task.result + circle.list / task |
| 6.6.5 | /robots/{wechatId}/circles/comment/delete | POST | 删除朋友圈评论 | 必填 | task.result / task |
| 6.6.6 | /robots/{wechatId}/circles/pull | POST | 拉取朋友圈列表 | 不校验(clientCmdId 仅回显) | circle.list / event |
| 6.7.1 | /robots/{wechatId}/conversations/read | POST | 标记会话已读 | 必填 | task.result / task(无 taskId,仅知成败) |
| 6.7.2 | /robots/{wechatId}/subscribe | POST | 消息订阅开关 | 必填 | message.received / event(通知型,见端点说明) |
| 6.8.1 | /robots/{wechatId}/emoji/fetch | POST | 拉取收藏表情列表 | 可选 | emoji.list / event |
| 6.8.2 | /robots/{wechatId}/emoji/add | POST | 添加表情到收藏 | 必填 | task.result / task |
| 6.9.1 | /robots/{wechatId}/labels | POST | 新建标签 | 可选 | task.result / task |
| 6.9.2 | /robots/{wechatId}/labels/{labelId} | DELETE | 删除标签 | — | task.result / task |
| 6.10.1 | /robots/{wechatId}/sender-config | POST | 下发发单配置 | 必填 | — / none(通知型) |
| 6.10.2 | (贵方实现的转链服务) | — | 发单转链接口 | — | —(设备侧调用) |
| 6.10.3 | /robots/{wechatId}/device-config | POST | 下发开关与消息过滤配置 | 必填 | — / none(通知型) |
| 6.10.4 | /robots/{wechatId}/device-config | GET | 查询设备当前配置 | — | —(同步应答,504 超时) |
| 6.11.1 | /fileUpload | POST | 媒体上传中转(设备内部链路) | — | —(§5.7) |
| 6.11.2 | /fileUploadMd5Check | POST | 秒传预检(恒未命中) | — | —(§5.7) |
| 6.12.1 | /ops/dead-letters | GET | 查询事件死信 | — | —(查询) |
| 6.12.2 | /ops/dead-letters/replay | POST | 重放事件死信 | — | —(操作型) |
| 6.12.3 | /robots/{wechatId}/logs/pull | POST | 拉取 APP 日志 | 可选 | logs.pulled / event |
| 6.12.4 | /debug/events | GET | 调试事件轮询(无代码测试) | — | —(查询) |
none 态;6.5.1 建群、6.3.2 撤回、6.7.2 订阅为 event 态(结果经业务事件感知)。诚实披露与对接建议见 §6.13.2。6.2 机器人管理
6.2.1 机器人列表快照
用途:拉取贵方名下全部机器人(含被踢设备)的在线状态快照。客户服务启动/重启时必须先调本接口重建初始视图,此后靠 robot.online/robot.offline 事件增量维护——事件为边缘触发(仅状态翻转时推送)且不重放,不拉快照将永远感知不到"一直在线"的设备。
请求:无 body。
curl -X GET "{网关地址}/sdk/v2/robots" \
-H "Authorization: Bearer {appId}" \
-H "X-Icaiy-Timestamp: {毫秒时间戳}" \
-H "X-Icaiy-Signature: {签名(空body)}"成功响应:
{
"code": 200, "message": "操作成功",
"data": {
"robots": [
{"wechatId": "1688857389", "online": true, "kicked": false},
{"wechatId": "1688857321", "online": false, "kicked": true}
]
}
}| 字段 | 说明 |
|---|---|
| wechatId | 机器人企微 ID |
| online | 本实例 TCP 通道 ‖ Redis 归属标记(跨实例视角;断连残留由 90s TTL 收敛) |
| kicked | 是否被踢设备(踢线者保留在名单中并标注,贵方可据此停止对其下发) |
6.2.2 查询机器人在线状态
用途:查询单个机器人是否在线。发送指令前的预检手段。
成功响应:data.online(bool);在线时可能携带 lastOnlineAt(毫秒,本次上线翻转时刻;状态未知时缺省)。
{
"code": 200, "message": "操作成功",
"data": {"online": true, "lastOnlineAt": 1760000000000}
}错误响应:2004(机器人不属于该客户)。回执/事件:无(查询)。
6.2.3 触发数据同步
用途:让机器人重新上报全量/组合数据。scope 为客户自定义组合(全量同步的标准由贵方定义,网关不做固定捆绑),可选值 groups/contacts/tags/account;数组顺序即下发顺序,项间间隔 800ms。
前置条件:机器人在线(2008)。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| scopes | string[] | 是 | 同步项数组,自动去重保序 |
curl -X POST "{网关地址}/sdk/v2/robots/1688857389/sync" \
-H "Authorization: Bearer {appId}" -H "X-Icaiy-Timestamp: {ts}" -H "X-Icaiy-Signature: {签名}" \
-H "Content-Type: application/json" \
-d '{"scopes":["groups","tags"]}'成功响应:
{
"code": 200, "message": "操作成功",
"data": {
"accepted": true, "scopes": ["groups", "tags"],
"note": "指令已异步下发, 数据经事件推送送达"
}
}错误响应:400 —— body 必须为 {"scopes":[...]}, 可选值: groups/contacts/tags/account(body 缺失/非 JSON/scopes 为空);未知 scope: {值} (可选: groups/contacts/tags/account)。
回执/事件:无任务级回执,数据按 §5.6.0 编排表 经事件流送达(注意 groups 是全家桶、60 秒去重窗口)。
6.2.4 修改机器人资料
用途:修改机器人头像 / 昵称 / 企微号别名 / 对外显示。四项均可选但至少一项生效。
前置条件:机器人在线(2008);幂等必填档。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| clientCmdId | string | 是 | 幂等 + 回执关联 |
| nickName | string | 否 | 昵称(不能为空串) |
| wechatNo | string | 否 | 企微号别名(不能为空串) |
| avatarUrl | string | 否 | 头像 http/https 直链(APP 自行下载) |
| externalNameType | int | 否 | 对外显示:0=不修改 / 1=实名 / 2=别名 |
wechatNo(APP 先改别名再切显示)。成功响应:
{
"code": 200, "message": "操作成功",
"data": {
"taskId": "1024", "receiptEvent": "task.result", "receiptMode": "task",
"clientCmdId": "cmd-20261028001"
}
}错误响应:400 —— nickName 不能为空串 / wechatNo 不能为空串 / avatarUrl 必须为 http/https URL / externalNameType 仅支持 0=不修改 / 1=实名 / 2=别名 / 至少传一项: nickName/wechatNo/avatarUrl/externalNameType。
回执/事件:task.result(taskType=ModifyProfileTask,taskId 关联 + clientCmdId 回填)。修改成功后的资料最终事实以 account.updated/robot.online 事件为准(可调 sync scopes=["account"] 主动刷新)。
6.2.5 获取机器人二维码
用途:拉取机器人个人二维码图片。APP 拉码后先上传(§5.7),经 robot.qrcode 事件回推图片 URL。
前置条件:机器人在线(2008);幂等必填档。
请求:clientCmdId(string,必填——缺失报 400:参数缺失(clientCmdId 必填))。
成功响应:
{
"code": 200, "message": "操作成功",
"data": {
"taskId": "1027", "receiptEvent": "robot.qrcode", "receiptMode": "event",
"clientCmdId": "cmd-20261028002"
}
}回执/事件:robot.qrcode(§5.6.16,成功带 qrcodeUrl,失败带 errorMsg)。设备端回执不携带 TaskId,clientCmdId 按机器人维度 FIFO 关联——同机器人并发拉码按先后顺序对应,请避免对同一机器人并发调用。
6.3 消息收发
6.3.1 发送消息
用途:驱动机器人向单聊好友或群聊发送消息,支持 11 种消息类型(text/image/video/voice/link/file/emoji/namecard/location/miniapp/channel)+ 引用 + 群内 @。
前置条件:机器人在线(2008);幂等必填档。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| clientCmdId | string | 是 | 幂等 + 回执关联 |
| friendId | string | 是 | 接收方(单聊=人 ID,群聊=群 ID) |
| msgType | string | 是 | 消息类型,见下表 |
| quoteMsgId | long | 否 | 被引用消息的企微本地 ID(= message.received 事件的 msgId);>0 时 APP 构造原生引用区块 |
| …类型专属字段 | — | 按类型 | 见下表 |
msgType 与类型专属字段:
| msgType | 专属字段 | 说明 |
|---|---|---|
text | text(必填);mentionIds(可选,string[]) | 文本原文直传;mentionIds 为群内 @ 提及的成员 ID 数组,传 ["0"] 表示 @所有人;可与 quoteMsgId 同条共存(企微原生支持引用+@ 同发) |
image | url(必填) | 图片 http/https 直链,APP 下载后发送 |
video | url(必填) | 视频 URL,APP 下载并生成缩略图 |
voice | url(必填)、duration(可选,1~60 秒,缺省 1,超出自动收敛) | APP 只认 SILK 直链,贵方 OSS 须自备 SILK 格式;非 SILK 时 message.receipt 回执失败,转码由贵方自理 |
link | url(必填);title / description / thumbUrl(可选) | 链接卡片 |
file | url(必填);fileName(可选) | 文件(fileName 供保存命名) |
emoji | emotionInfo(必填) | 收到的表情消息载荷 EmotionInfo 原文(JSON 字符串),或 emoji.list 事件的 item 原样回传 |
namecard | cardId(必填,目标联系人的企微 ID);cardName / cardAvatar / cardCorp / cardJob(可选) | 名片 |
location | latitude + longitude(必填);title / address(可选) | 位置 |
miniapp | appId(必填,兼容小写 appid);title / pagePath(兼容 pagepath)/ appName / username / iconUrl / thumbUrl / description(兼容 desc)(可选) | 小程序卡片;收到小程序消息原样转发时无需手工改字段名 |
channel | finderJson(必填) | 视频号消息 content 原文(JSON 字符串),零处理纯透传 |
请求示例(文本 + 群内 @):
curl -X POST "{网关地址}/sdk/v2/robots/1688857389/messages" \
-H "Authorization: Bearer {appId}" -H "X-Icaiy-Timestamp: {ts}" -H "X-Icaiy-Signature: {签名}" \
-H "Content-Type: application/json" \
-d '{"clientCmdId":"cmd-001","friendId":"WRaaaa@im.chatroom","msgType":"text","text":"下午三点开会","mentionIds":["7881300998xxx"]}'成功响应:
{
"code": 200, "message": "操作成功",
"data": {
"taskId": "102931", "receiptEvent": "message.receipt", "receiptMode": "task",
"clientCmdId": "cmd-001"
}
}taskId 即网关合成的 MsgId——撤回该消息时用它(§6.3.2)。错误响应(400):参数缺失(clientCmdId/friendId/msgType 必填);不支持的 msgType: {值} (可选: text/image/voice/video/link/file/emoji/namecard/location/miniapp/channel);各类型缺字段时报具体字段(如 text 类型必须传 text 字段、location 类型必须传 latitude + longitude、namecard 类型必须传 cardId(目标联系人的企微ID)、emoji 类型必须传 emotionInfo 字段(收到的表情消息载荷 EmotionInfo 原文)、channel(视频号) 类型必须传 finderJson 字段(收到的视频号消息 content 原文)、URL 类字段报 xxx 必须为 http/https URL)。
引用消息注意:传 quoteMsgSvrId 会被拒绝——引用消息请传 quoteMsgId(企微本地ID, 取 message.received 的 msgId), 不支持 msgSvrId。
quoteMsgId 两级查找被引用消息——①内存缓存(设备收到过该消息即写入,quoteMsgId 本就来自 message.received 上报,正常必然命中);②企微本地消息库(本地 msgId 持久化,设备重启/缓存淘汰后仍可查到)。两级均未命中的唯一场景是"被引用消息已在设备企微中被人为删除"——此时该消息静默降级为普通文本发出,无失败回执;其他场景引用区块必然生效。回执/事件:message.receipt(§5.6.3,receiptMode=task):APP 执行完毕(约 1~2 秒)回执成败,事件携带 clientCmdId 与 msgId(下发 MsgId 原值)、msgSvrId(服务端消息 ID)。最终成败以回执为准。
6.3.2 撤回消息
用途:撤回机器人自己发出的消息。
前置条件:机器人在线(2008);幂等必填档。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| clientCmdId | string | 是 | 幂等键 |
| friendId | string | 是 | 会话对方(单聊=人 ID,群聊=群 ID) |
| msgId | long | 是 | 企微本地消息 ID——取 §6.3.1 受理响应的 taskId(或 message.receipt 回执的 msgId);不是 msgSvrId |
成功响应:data.taskId + receiptEvent: "message.revoked" + receiptMode: "event"。
错误响应(400):friendId 必填;msgId 必填(企微本地消息ID, 取自 message.receipt 回执的 MsgId);msgId 必须为正数(企微本地消息ID)。
message.revoked 事件(§5.6.4)感知(事件带 msgSvrId 可关联原消息);失败场景(发送超 2 分钟 / 无撤回权限)设备端静默,不会收到任何失败信号,贵方请以本地时间窗自行约束。6.3.3 拉取历史消息
用途:拉取指定会话的历史聊天记录,结果经 history.batch 事件分批回推(每批 20 条)。
前置条件:机器人在线(2008);幂等必填档。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| clientCmdId | string | 是 | 幂等键 |
| friendId | string | 是 | 会话对方(单聊=人 ID,群聊=群 ID) |
| count | int | 否 | 单批条数,缺省 20,上限 100(超出自动收敛) |
| startTime | long | 否 | 起始时间(秒,设备端口径) |
| endTime | long | 否 | 锚点 msgSvrId:0=从最新开始拉;>0=拉该锚点消息之前的记录(翻页锚点) |
成功响应:data.taskId + receiptEvent: "history.batch" + receiptMode: "event"。
错误响应(400):friendId 必填(单聊=人ID, 群聊=群ID)。
回执/事件:history.batch(§5.6.12,items 带 peerId/contentType/content/msgId/msgSvrId/selfSent/timestamp/status);翻页请以事件中最后一条的 msgSvrId 作为下一次的 endTime。
6.3.4 语音转文字
用途:将一条语音消息转为文字。
前置条件:机器人在线(2008);幂等可选档(不传 clientCmdId 直接执行,传入则幂等)。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| clientCmdId | string | 否 | 幂等键 |
| friendId | string | 是 | 好友 ID |
| msgSvrId | long | 是 | 语音消息的服务端消息 ID(message.received 事件的 msgSvrId),须 >0 |
成功响应:data.taskId + receiptEvent: "voice.transcript" + receiptMode: "event"。
错误响应(400):参数缺失(friendId 必填: 好友ID);参数缺失(msgSvrId 必填: 语音消息的服务端消息ID, 取 message.received 的 msgSvrId)。
回执/事件:voice.transcript(§5.6.13 表外字段见 §5.6.1 总览;payload 含 state 0-6 与 transText 文字结果,taskId 关联)。
6.4 好友管理
6.4.1 通过 / 拒绝好友申请
用途:处理收到的好友申请(通过或拒绝)。通过成功后机器人即与对方建立好友关系。
前置条件:机器人在线(2008);幂等必填档。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| clientCmdId | string | 是 | 幂等键 |
| friendId | string | 是 | 申请人 ID |
| operation | int | 否 | 1=通过(默认)/ 2=拒绝;其他值报 400:operation 仅支持 1=通过 / 2=拒绝 |
| ticket | string | 否 | 申请凭证(可选,APP 端执行只依赖 friendId) |
| remark / source | string/int | 否 | 通过时的备注 / 来源 |
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
回执/事件:双通道——① task.result(taskType=AcceptFriendAddRequestTask,通过/拒绝操作成败,taskId 关联 + clientCmdId 回填);② 通过成功另有 contact.add / contact.sync 数据流。拒绝操作仅有 ① 可感知(无任何数据事件)。
6.4.2 修改好友备注
用途:修改客户备注名(与描述)。
请求:clientCmdId(必填)、friendId(必填)、remark(必填)、description(可选)。缺失报 400:friendId + remark 必填。
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
回执/事件:task.result(taskType=ModifyFriendMemoTask,taskId 关联 + clientCmdId 回填);生效后客户资料经后续 contacts 同步刷新(资料变更无实时事件,见 §5.6.8 感知策略)。
6.4.3 删除好友
用途:删除客户/好友。
请求:clientCmdId(必填)、friendId(必填,缺失报 400:friendId 必填)。
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
回执/事件:task.result(taskType=DeleteFriendTask)。注意:APP 不上报删除通知(设备端 1023 仅 proto 定义、不产生事件),删除成功以本回执为准,贵方收到成功回执后自行删除本地数据。
6.4.4 刷新单个客户资料
用途:按需刷新单个客户的最新完整资料(下发单客户拉取指令 → APP 拉取 → contact.updated 事件回推)。典型场景:本地客户资料疑似过期(客户反馈/长期未同步)时按需刷新,替代全量 contacts 对账。
请求:POST /robots/{wechatId}/contacts/{contactId}/refresh——contactId 在 URL 路径中;body 携带 clientCmdId(必填)。contactId 接受纯数字或 R: 前缀写法(网关自动剥离后校验纯数字)。错误:400 contactId 不能为空 / contactId 必须为纯数字客户 ID(可带 R: 前缀, 网关自动剥离)。
成功响应:data.receiptEvent: "contact.updated" + receiptMode: "event"(无 taskId——结果经数据事件感知)。
回执/事件:contact.updated(§5.6.8,item 与 contact.add 同构)。
6.4.5 给客户打标签
用途:设置客户的标签集合。labelIds 全量覆盖(传空数组=清空该客户全部标签)。
请求:clientCmdId(必填)、friendId(必填)、labelIds(int 数组;缺省视为空数组)。
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
回执/事件:task.result(taskType=ContactSetLabelTask,taskId 关联 + clientCmdId 回填)。标签 ID 全量列表经 tag.list 事件维护(§5.6.11);标签的增删管理见 §6.9。
6.4.6 批量主动加好友
用途:按手机号列表批量搜索并发送好友申请(批次级指令)。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| clientCmdId | string | 是 | 幂等键 |
| phones | string[] | 是 | 目标手机号数组(缺失报 400:phones 必填(目标手机号数组)) |
| verifyMsg | string | 否 | 验证消息 |
| remark | string | 否 | 通过后备注 |
| targetType | string | 否 | wecom(默认,仅企微)/ wechat(仅个微)/ any;非法报 400:targetType 非法(可选 wecom/wechat/any) |
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
task.result(taskType=AddFriendsTask,批次级:整批仅一条,success + errMsg(all failed/send nothing/part failed)标识批次成败);② 成功加上的好友经 contact.add/contact.sync 感知。逐条粒度无回执——筛不到对应账号类型的手机号被跳过且无通知;需要逐条明确结果的场景请用 §6.4.8(单手机号闭环)。6.4.7 搜索账号候选(只搜不加)
用途:按手机号搜索目标账号(同一手机号可能同时绑定个微+企微),只搜不添加——先看"有哪些人可加"再决定。
请求:clientCmdId(必填)、phone(必填;自动去空格/连字符,归一化后长度须 5~20 位,否则报 400:phone 长度非法(5~20位))。
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
回执/事件:task.result(taskType=FindContactTask,taskId 关联 + clientCmdId 回填),事件 data 字段为候选列表 JSON:[{"type":"wecom"|"wechat","name":"…","remoteId":"…"}…]。确认添加用 §6.4.8。
6.4.8 搜索并直接加好友
用途:单手机号闭环版——搜索 + 直接发送好友申请,每条有明确结果回执(成功/失败如"该用户不存在"均可感知)。
请求:clientCmdId(必填)、phone(必填,同 §6.4.7 归一化与长度校验)、verifyMsg/remark(可选)、targetType(可选,默认 wecom——企微获客场景下搜到个微候选误加无意义;any 保留旧行为取搜索结果第一个)。
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
回执/事件:task.result(taskType=FindContactTask,成功/失败均回执);加成好友后另有 contact.add 事件。
6.4.9 僵尸粉检测
用途:对机器人好友列表发起僵尸粉(单向好友)检测,可选"只检不删"。过程与终态双事件回推。
前置条件:机器人在线(2008);幂等可选档;请求体可省略。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| clientCmdId | string | 否 | 幂等键 |
| onlyCheck | bool | 否 | true=只检测不删除(默认 false=检测并删除僵尸粉) |
| skipHour | int | 否 | 跳过 N 小时内活跃的好友(默认 0;负数报 400:skipHour 不能为负(跳过 N 小时内活跃好友)) |
| max | int | 否 | 单轮检测上限,0=不限(默认 0;负数报 400:max 不能为负(单轮检测上限, 0=不限)) |
成功响应:data.taskId + receiptEvent: "friends.detect.result" + receiptMode: "event"。
回执/事件:过程 friends.detect.progress(2028,周期计数)+ 终态 friends.detect.result(1280,zombies/blockedList 名单),均携带 taskId(§5.6.1 总览)。检测出的僵尸粉可用 §6.4.10 重加。
6.4.10 僵尸粉重加
用途:向被删除的单向好友(僵尸粉)重新发送好友申请。
前置条件:机器人在线(2008);幂等可选档。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| clientCmdId | string | 否 | 幂等键(传入时 task.result 按 taskId 回填关联) |
| friendId | string | 是 | 好友 ID——取 friends.detect.result 事件的 zombies 项 |
| message | string | 是 | 验证消息/打招呼话术 |
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
错误响应(400):参数缺失(friendId 必填: 好友ID, 取 friends.detect.result 事件的 zombies 项);参数缺失(message 必填: 验证消息/打招呼话术)。
回执/事件:task.result(taskType=AddFriendWithSceneTask);申请通过后另有 contact.add 事件。
6.5 群管理
6.5.1 群操作(建群/改名/公告/邀请/踢人/退群/转让)
用途:一个端点覆盖全部群管理动作(对齐 APP ChatRoomActionTask 单协议设计),以 action 字段区分。
前置条件:机器人在线(2008);幂等必填档。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| clientCmdId | string | 是 | 幂等键 |
| action | string | 是 | create(建群)/ rename(改名)/ notice(群公告)/ invite(邀请入群)/ kick(踢人)/ transfer_owner(转让群主)/ exit(退群);缺失报 400:action 必填(可选: create/rename/notice/invite/kick/transfer_owner/exit),非法报 400:不支持的 action: {action} (可选: …) |
| groupId | string | 视 action | 群 ID——rename/notice/invite/kick/transfer_owner/exit 必填 |
| memberIds | string[] | 视 action | 成员 ID 数组——create(至少 1 个,即初始成员)与 invite/kick 必填 |
| name | string | 视 action | rename 必填(新群名);create 可选(新群名) |
| notice | string | 视 action | notice 必填(公告内容) |
| ownerId | string | 视 action | transfer_owner 必填(新群主成员 ID) |
错误响应(400,具体到 action):create 必填 memberIds(至少 1 个成员 ID);rename 必填 groupId + name;notice 必填 groupId + notice;invite 必填 groupId + memberIds / kick 必填 groupId + memberIds;transfer_owner 必填 groupId + ownerId;exit 必填 groupId。
成功响应:data.taskId(受理凭证)+ receiptEvent: "task.result" + receiptMode: "task";clientCmdId 回显。
回执/事件:双通道——① task.result(taskType=ChatRoomActionTask,除 create 外全部动作的操作成败,taskId 关联 + clientCmdId 回填;频控/群满/无权限等失败由此感知);② 数据流并存:create 成功 → group.add(新群数据,结构同 §5.6.9 group.sync 元素);其余动作 → group.changed(群结构/字段变化)。退群成功后该群从 group.sync 列表消失。建群(create)无任务级回执,以 group.add 数据流为准(见 §6.13.2)。
6.5.2 拉取指定群成员列表
用途:拉取单个群的全量成员明细。典型场景:收到 group.changed(成员进出)后拉取新名单。
前置条件:机器人在线(2008);幂等必填档。
请求:路径 groupId——接受纯数字 / R: 前缀 / @im.chatroom 后缀写法,网关统一剥离后校验(空报 400:groupId 不能为空;非纯数字报 400:groupId 必须为纯数字群 ID(可带 R: 前缀/@im.chatroom 后缀, 网关自动剥离));请求体仅需 clientCmdId(必填)。
成功响应:data.receiptEvent: "group.members" + receiptMode: "event"(无 taskId——数据事件即回执);clientCmdId 回显。
回执/事件:group.members 事件按 groupId 关联,单群全量成员一次回推(结构见 §5.6.1 事件总览)。
6.5.3 获取群二维码
用途:让 APP 拉取指定群的入群二维码并上传,回推图片 URL。
前置条件:机器人在线(2008);幂等可选档;请求体可省略。
请求:clientCmdId(否)、groupId(是;纯数字群 ID,缺失报 400:参数缺失(groupId 必填: 群ID, 纯数字, 取 group.sync 事件的 id))。
成功响应:data.taskId + receiptEvent: "group.qrcode" + receiptMode: "event"。
回执/事件:group.qrcode 事件——APP 拉码并上传后回推(1043:filePath=图片 URL / success / errorMsg,结构见 §5.6.1 事件总览)。
6.5.4 群邀请审批
用途:同意(审批通过)入群邀请。三个参数均取群邀请消息(message.received,msgType=groupInvite)的原始字段。
前置条件:机器人在线(2008);幂等可选档;请求体可省略。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| clientCmdId | string | 否 | 幂等键(传入时 task.result 按 taskId 回填关联) |
| roomId | string | 是 | 群 ID(缺失报 400:参数缺失(roomId 必填: 群ID)) |
| msgSvrId | long | 是 | 群邀请消息的服务端消息 ID(>0;缺失报 400:参数缺失(msgSvrId 必填: 群邀请消息的服务端消息ID)) |
| msgContent | string | 是 | 群邀请消息原始 content(取 message.received 的 content 原文) |
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
回执/事件:task.result(taskType=ChatRoomInviteApproveTask,taskId/clientCmdId 关联);审批成功另有群事件流(group.members / group.add)佐证。
6.5.5 扫码进群
用途:通过群二维码链接进群。
前置条件:机器人在线(2008);幂等可选档;请求体可省略。
请求:clientCmdId(否)、qrUrl(是;群二维码链接,支持企微二维码 URL 或二维码解析内容;缺失报 400:参数缺失(qrUrl 必填: 群二维码链接, 支持企微二维码 URL 或二维码解析内容))。
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
回执/事件:task.result(taskType=JoinGroupByQrTask,taskId/clientCmdId 关联);进群成功另有群事件流(group.add / group.members)佐证。
6.6 朋友圈
6.6.1 发布朋友圈
用途:以机器人身份发布朋友圈(纯文本 / 带附件)。
前置条件:机器人在线(2008);幂等必填档。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| clientCmdId | string | 是 | 幂等键 |
| content | string | 二选一 | 文本内容——content 与 attachContent 至少传一个,否则报 400:content(文本) 与 attachContent(附件)至少传一个 |
| attachContent | string | 二选一 | 附件内容(随 attachType 语义:链接 / 图片 / 视频地址) |
| attachType | int | 否 | 附件类型:0=Link 2=Picture 3=ShortVideo 4=LongVideo 5=ShiPinHao |
| visibleType | int | 否 | 可见范围:0=公开,其余按 APP 可见范围枚举透传(配合 visibleLabels/visibleFriends) |
| visibleLabels | string | 否 | 可见标签(配合可见范围枚举) |
| visibleFriends | string | 否 | 可见好友(配合可见范围枚举) |
| comment | string | 否 | 随发评论 |
| sendSlow | bool | 否 | 慢速发送开关(透传 APP) |
成功响应:data.taskId + receiptEvent: "circle.published" + receiptMode: "event";clientCmdId 回显。
回执/事件:circle.published 事件(发布结果,结构见 §5.6.1 事件总览)——带 clientCmdId(发布指令受理时登记 taskId 关联,事件到达时按 taskId 回填),可精确关联本次发布;receiptMode 为 event(事件流感知),失败(如内容审核不通过)同样经该事件回推。
6.6.2 删除朋友圈
用途:删除机器人自己发布的朋友圈。
前置条件:机器人在线(2008);幂等必填档。
请求:clientCmdId(必填)、circleId(必填正整数;缺失/非法报 400:circleId 必填(正整数))。
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
回执/事件:task.result(taskType=DeleteSNSNewsTask,taskId/clientCmdId 关联)。
6.6.3 朋友圈点赞
用途:给朋友圈点赞;cancel=true 取消点赞。
前置条件:机器人在线(2008);幂等必填档。
请求:clientCmdId(必填)、circleId(必填正整数,同 §6.6.2 校验)、cancel(否,bool,默认 false)。
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
回执/事件:task.result(taskType=CircleLikeTask,taskId/clientCmdId 关联)。
6.6.4 朋友圈评论
用途:评论朋友圈;replyCommentId>0 时为回复指定评论。
前置条件:机器人在线(2008);幂等必填档。
请求:clientCmdId(必填)、circleId(必填正整数)、content(必填评论内容;二者缺一报 400:circleId + content 必填)、toWeChatId(否,目标人 ID,透传 APP 指定评论对象)、replyCommentId(否,long,>0 时回复该条评论)。
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
回执/事件:双通道——① task.result(taskType=CircleCommentReplyTask,评论/回复操作成败,taskId 关联 + clientCmdId 回填);② 评论生效后经 circle.list 数据流感知(拉取到最新评论即确认展示,见 §6.6.6)。
6.6.5 删除朋友圈评论
用途:删除朋友圈下的指定评论。
前置条件:机器人在线(2008);幂等必填档。
请求:clientCmdId(必填)、circleId(必填正整数)、commentId(必填正整数;缺失报 400:circleId + commentId 必填(正整数))。
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
回执/事件:task.result(taskType=CircleCommentDeleteTask,taskId/clientCmdId 关联)。
6.6.6 拉取朋友圈列表
用途:拉取朋友圈列表——不传 friendId 拉自己的,传 friendId 拉指定好友的。
前置条件:机器人在线(2008)。
请求:clientCmdId(否,仅回显进响应 data,不做幂等校验——重复调用会重复下发)、friendId(否,好友 ID)、startTime(否,long,拉取起始秒级时间戳,缺省 0)。
成功响应:data.taskId(受理凭证)+ receiptEvent: "circle.list" + receiptMode: "event";clientCmdId 仅回显。
回执/事件:无任务级回执——朋友圈数据经 circle.list 事件按批次回推(不传 friendId 触发自己的朋友圈推送,传 friendId 触发好友朋友圈拉取),结构见 §5.6.1 事件总览。以 circle.list 数据到达作为本指令的结果感知。
6.7 会话与订阅
6.7.1 标记会话已读
用途:把某会话(单聊或群聊)标记为已读。
前置条件:机器人在线(2008);幂等必填档。
请求:clientCmdId(必填)、friendId(必填;单聊=人 ID,群聊=群 ID;缺失报 400:friendId 必填(单聊=人ID, 群聊=群ID))。
成功响应:data.receiptEvent: "task.result" + receiptMode: "task"(无 taskId);clientCmdId 回显。
task.result(taskType=TriggerMessageReadTask)无法按指令关联——proto 无 TaskId(APP 自生成),回执不携带 taskId/clientCmdId 关联键,仅机器人维度感知成败(知道"该机器人有一条已读回执",无法对应到具体指令)。6.7.2 消息订阅开关
用途:开启/关闭好友消息上报——成功即 APP 开始(或停止)上报 message.received 事件。
前置条件:机器人在线(2008);幂等必填档。
请求:clientCmdId(必填)、subscribe(必填 bool,缺失报 400:subscribe 必填(bool);true=开始上报 / false=停止)、friendIds(否,string[]——不传=全部好友,传=仅指定好友)。
成功响应:data.receiptEvent: "message.received" + receiptMode: "event";clientCmdId 回显。
message.received 事件开始(或停止)到达。关闭订阅后设备端不再上报,恢复需重新下发 subscribe=true。6.8 表情收藏
6.8.1 拉取收藏表情列表
用途:拉取机器人的企微收藏表情列表,供发消息(§6.3.1 emoji 类型)时选用。
前置条件:机器人在线(2008);幂等可选档;请求体可仅含 clientCmdId。
请求:clientCmdId(否,传入时 emoji.list 事件按 taskId 回填关联)。
成功响应:data.taskId + receiptEvent: "emoji.list" + receiptMode: "event"。
回执/事件:emoji.list 事件——items 元素与企微 EmotionInfo 同构,发消息时 emotionInfo 字段原样回传即可(§6.3.1)。
6.8.2 添加表情到收藏
用途:把一张图片加入企微收藏表情分组(APP 下载 url 图片后收藏)。
前置条件:机器人在线(2008);幂等必填档。
请求:clientCmdId(必填,缺失报 400:参数缺失(clientCmdId 必填))、url(必填,表情图片 URL,通常取收到表情消息载荷的 url 字段;缺失报 400:url 必填(表情图片URL, 通常取收到表情消息载荷的 url 字段))、md5(否,图片 MD5)。
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
回执/事件:task.result(taskType=AddEmojiTask,taskId/clientCmdId 关联);收藏成功后可用 §6.8.1 拉取列表验证。
6.9 标签管理
6.9.1 新建/改名标签
用途:新建机器人通讯录标签(labelId 缺省或 0),或给已有标签改名(labelId>0 + labelName)。
前置条件:机器人在线(2008);幂等可选档;请求体可省略(省略或非 JSON 报 400:请求体不是合法 JSON)。
请求:clientCmdId(否,传入时 task.result 按 taskId 回填关联)、labelId(否,long——缺省/0=新建,>0=改名)、labelName(是,标签名;缺失报 400:参数缺失(labelName 必填: 标签名))。
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
回执/事件:task.result(taskType=ContactLabelTask,taskId/clientCmdId 关联);变更结果另经标签数据流(tag.list 事件)间接感知(§6.4.5 打标签引用此处的标签 ID)。
6.9.2 删除标签
用途:删除机器人通讯录标签。
前置条件:机器人在线(2008);无请求体、无幂等键(DELETE 无 body)。
请求:路径 labelId——必须为正整数(非法报 400:labelId 非法(必须为正整数, 取 tag.list 事件的 id))。
成功响应:data.taskId + receiptEvent: "task.result" + receiptMode: "task"。
回执/事件:task.result(taskType=ContactLabelDeleteTask);删除结果另经 tag.list 事件间接感知。
6.10 配置下发
本组共 3 个网关端点 + 1 个贵方实现的转链契约。/sender-config 与 /device-config 均为通知型指令:下发成功即生效,无任务回执(receiptMode: "none");核对落地结果用 §6.10.4 同步查询。
6.10.1 下发发单配置
用途:向设备下发发单开关与发单计划(覆盖式下发,非增量/版本控制——每次下发完整替换设备端计划)。
前置条件:机器人在线(2008);幂等必填档。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| clientCmdId | string | 是 | 幂等键 |
| sender | bool | 是 | 发单开关布尔位(缺失报 400:sender 必填(bool);见下方「sender 字段说明」) |
| senderConfig | string | 是 | 发单配置 JSON 字符串(缺失/空报 400:senderConfig 必填(string JSON)) |
成功响应:data.receiptMode: "none"(无 receiptEvent、无 taskId);clientCmdId 回显。
senderConfig 结构(设备端消费语义):
{
"sendInterval": "3000-4000",
"shillPlanModelList": [
{
"shillPlanTargetId": 101,
"planTargetRoomId": "987654",
"planTargetSwitch": "1",
"tbkSwitch": true,
"convertFailMode": "…",
"convertFailCustomMsg": "…",
"planSourceList": [
{
"shillPlanSourceId": 1,
"planSourceRoomId": "123456",
"planSourceSwitch": "1",
"planSourceWechatId": "16888xxxx",
"planSourceMemberId": ""
}
]
}
]
}| 字段 | 类型 | 说明 |
|---|---|---|
| sendInterval | string | 随机发送间隔毫秒区间(如 3000-4000=每条间隔 3~4 秒随机);缺省/空回退默认 3000-4000 |
| shillPlanModelList | array | 发单计划数组(覆盖式) |
| shillPlanTargetId | int | 计划 ID(forward.log 转发日志按此关联) |
| planTargetRoomId | string | 转发目标群 ID(可多个) |
| planTargetSwitch | string | "1"=启用该计划 |
| tbkSwitch | bool | 是否走转链(true=转发前调用 §6.10.2 转链接口) |
| convertFailMode / convertFailCustomMsg | string | 转链失败处理模式 / 自定义兜底话术 |
| planSourceList[] | array | 采集来源列表:shillPlanSourceId(来源 ID)、planSourceRoomId(采集群 ID)、planSourceSwitch("1"=启用)、planSourceWechatId(来源群所属机器人)、planSourceMemberId(指定采集成员,按成员采集时传) |
shillPlanModelList非空 → 覆盖落盘并开始发单;shillPlanModelList空数组/缺省 → 清空本地发单配置并停发;senderConfig空串 → 仅停发(保留已落盘计划数据);- JSON 解析失败 → 保持旧配置不变(不会半应用),日志携带配置片段便于定位字段类型错误;
- sender 字段说明:
sender为网关必填布尔位并随指令下发,但设备端发单开关实际由senderConfig内容驱动(上述语义),该字段当前不参与设备端判定。
- APP 本地持久化最近一次配置,正常重连不丢;
- 设备重装/清数据/换机后本地配置清零——收到
robot.online后请重新下发/sender-config恢复发单; - 机器人被停用/删除/解绑/过期 → 下发空
shillPlanModelList(或空 senderConfig)停发;解绑时服务端会同步清理设备端残留配置。
6.10.2 发单转链接口(贵方实现)
用途:发单计划开启转链(tbkSwitch=true)时,设备在转发消息前调用贵方实现的 HTTP 转链服务,把原始内容中的链接转成贵方渠道链接(如淘客转链),再按转换后内容发单。
地址与超时:路径固定 POST /api/shillPlan/batchForwardContentDispose;基础地址优先取设备登录时下发的独立转链地址(指向贵方自建转链服务),未配置时回退 http://{设备当前接入服务器地址}:9999。超时 10 秒;超时/连接异常/非 JSON/code!=200/缺 data → 一律回退直发,不阻塞发单。
请求体(设备 → 贵方):
| 字段 | 类型 | 说明 |
|---|---|---|
| wechatId | string | 执行发单的机器人 ID |
| mobile | string | 设备登录手机号 |
| shillPlanTargetId | int | 发单计划 ID |
| content | string | 原始消息文本 |
| groupIdList | string[] | 目标群 ID 数组(裸群 ID) |
| method | string | 固定 batchForwardContentDispose |
| timestamp | string | 请求时间戳 |
| sign | string | 设备侧固定签名 f379eaf3c831b04de153469d1bec345e(贵方可据此校验请求来源) |
响应(贵方 → 设备,Content-Type: application/json)——三种形态:
| 形态 | 响应 | 设备行为 |
|---|---|---|
| 逐群转链 | {"code":200,"data":{"converted":true,"contentMap":{"{群ID}":"转换后内容"}}} | contentMap 的 key 必须与 groupIdList 逐字符一致,按群发送对应内容 |
| 统一转链 | {"code":200,"data":{"converted":true,"content":"转换后全文"}} | content 与原文不同时,全部目标群统一发送转换后内容 |
| 无需转链 | {"code":200,"data":{"converted":false}}(或 content 为空/与原文相同) | 走直发(正常结果,非失败) |
兜底:转链失败的场景兜底由计划内 convertFailMode/convertFailCustomMsg 承接(§6.10.1)。
6.10.3 下发开关与消息过滤配置
用途:下发设备行为开关与消息过滤策略(msgFilter)。
前置条件:机器人在线(2008);幂等必填档。
请求:clientCmdId(必填)、deviceConfig(必填,JSON 字符串;缺失/空报 400:deviceConfig 必填(string JSON))。
成功响应:data.receiptMode: "none";clientCmdId 回显。
deviceConfig 结构:
{
"joinRoomSwitch": "1",
"passFriendSwitch": "1",
"imgToEmojiSwitch": "0",
"imgZipSwitch": "1",
"msgFilter": {
"serving": 1,
"blockedIds": "123456,987654",
"allowedIds": "",
"keywordEnabled": 1,
"exactKeywords": "最低价,清仓",
"fuzzyKeywords": "加微信"
}
}| 字段 | 类型 | 说明 |
|---|---|---|
| joinRoomSwitch | string | 自动进群开关("1" 开 / "0" 关) |
| passFriendSwitch | string | 自动通过好友开关 |
| imgToEmojiSwitch | string | 图片转表情开关 |
| imgZipSwitch | string | 图片压缩开关 |
| msgFilter | object | 可选——节点缺省=本次不改动过滤策略;节点内按字段级合并(本次出现的字段覆盖、未出现的保留旧值),合并后快照持久化、进程重启自动恢复 |
msgFilter 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| serving | int | 全局接待门禁:1=上报 / 0=全拦。与 §6.7.2 订阅开关操作同一设备端开关——两条通道谁后下发谁生效,不产生双状态 |
| blockedIds | string | 黑名单(不看哪些):逗号分隔会话 ID(单聊=客户 ID、群聊=群 ID,纯数字、无 R: 前缀) |
| allowedIds | string | 白名单(只看哪些):同格式;空=不启用;非空时仅名单内会话实时消息上报 |
| keywordEnabled | int | 关键词过滤开关(1/0) |
| exactKeywords | string | 精确关键词,逗号分隔(整段完全相等才命中) |
| fuzzyKeywords | string | 模糊关键词,逗号分隔(子串包含即命中) |
判定优先级(设备端在消息上报唯一入口前置执行):serving=0 全拦 → 白名单(非空且不在名单→拦)→ 黑名单命中 → 关键词命中 → 放行。
message.receipt/forward.log/撤回关联等自发链路完整;被过滤消息不产生 message.received,媒体在下载/上传前拦截(零流量零存储);§6.3.3 主动拉取的历史消息不受该策略影响。6.10.4 查询设备当前配置
用途:同步查询设备当前生效配置快照——核对"设置过去之后配置到底是啥情况"。
请求:GET /sdk/v2/robots/{wechatId}/device-config,无请求体(签名按空 body 计算,§4)。
机制:网关下发 QueryDeviceConfigTask(1110) → 设备回传 DeviceConfigQueryResultNotice(3096) → HTTP 同步返回,超时窗 5 秒。
成功响应:
| 字段 | 类型 | 说明 |
|---|---|---|
| senderOn | string | 发单开关:"1"=发单中 / "0"=停发 |
| senderConfig | string | 设备端当前发单配置快照(原样 JSON 字符串,结构同 §6.10.1) |
| deviceConfig | string | 设备端当前开关配置快照(原样 JSON 字符串,结构同 §6.10.3,含 msgFilter 与 serving) |
错误响应:2008 机器人不在线;504 设备应答超时(5s): 设备 TCP 虽在线但未回传配置, 可能 APP 版本过低或假死, 稍后重试。
6.11 媒体上传(设备内部链路)
两个端点为设备内部链路(X-Access-Token 设备鉴权,非客户 API),贵方仅在自建对象存储/中转场景需要理解其契约——完整说明(设备令牌、multipart、网关流式中转、响应转译、大小上限)见 §5.7。
| 编号 | 路径 | 方法 | 用途 |
|---|---|---|---|
| 6.11.1 | /fileUpload | POST | 媒体上传中转:设备 multipart 上传 → 网关按设备归属取 mediaUploadUrl 流式中转 → 客户存储返回 URL → APP 契约响应 |
| 6.11.2 | /fileUploadMd5Check | POST | 秒传预检(当前恒返回 hit=false,占位端点) |
要点回顾:网关转发时携带 X-Icaiy-App-Id/X-Icaiy-Timestamp/X-Icaiy-Signature(HMAC 对文件字节计算),URL query 附 folder/filename/wechatId;客户返回 {"code":0,"data":{"url":"…"}} 时网关转译为 APP 契约;大小上限 max-file-size-mb(默认 100MB)。
6.12 运维与诊断
6.12.1 死信查看
用途:查询本客户的北向转发死信(事件投递重试耗尽后进入死信队列,保留 24 小时/最多 1000 条,机制见 §5.4)。
请求:GET /sdk/v2/ops/dead-letters,无请求体(签名按空 body 计算)。
成功响应:data.count(死信总数)+ data.letters(死信预览,最多 100 条)。
说明:只读查询,无幂等键;死信持续增长说明贵方接收端异常,请结合 letters 内容定位后用 §6.12.2 重放。
6.12.2 死信重放
用途:把本客户当前全部死信重新投递一遍(贵方接收端恢复后补偿丢事件)。
请求:POST /sdk/v2/ops/dead-letters/replay,请求体可省略;无幂等键(重放全部死信,天然幂等)。
成功响应:data.replayed(本次重放条数)。
6.12.3 拉取 APP 日志
用途:触发 APP 上传本地日志文件,用于排查设备侧行为(上传完成后 fileUrl 经事件回推)。
前置条件:机器人在线(2008);幂等可选档。
请求:请求体必须为合法 JSON(可为 {},否则报 400:请求体不是合法 JSON);clientCmdId(否);logDate(否,MMdd 四位数字如 1019,缺省=当天;非法报 400:logDate 格式非法(要求 MMdd 四位数字, 如 1019; 缺省=当天))。
成功响应:data.taskId + receiptEvent: "logs.pulled" + receiptMode: "event"。
回执/事件:logs.pulled 事件(fileUrl=日志文件下载 URL,结构见 §5.6.1 事件总览)。
6.12.4 调试事件轮询(无代码测试)
用途:轮询本客户最近产生的事件,用于尚未实现回调端点时闭环验证(机制见 §3.6「无代码测试闭环」)。返回内容即实际推送客户的事件原文(信封结构同 §5.3),所见即所推——正式接入后收到的就是这些内容。
前置条件:无。与 callbackUrl 是否配置、回调投递成败完全解耦(事件产生时即落缓冲,投递失败也不影响此处可查)。
请求:GET /sdk/v2/debug/events,无请求体(签名按空 body 计算);limit(否,1~1000,缺省 50,越界自动收敛到边界)。
成功响应:data.events(数组,新→旧,每条为完整事件信封 specVersion/eventId/eventType/occurredAt/robot/payload)。
说明:只读查询,无幂等键;缓冲保留 24 小时、每客户最多 1000 条(环形挤旧);按 appId 隔离,仅能读到本客户自己的事件;生产环境若不希望暴露此调试端点,可在网关层限制 /sdk/v2/debug/* 或将 gateway.debug-event-buffer.enabled 置为 false(关闭后本端点恒返回空数组,回调投递不受影响)。
# 轮询最近 50 条事件(每 3 秒一次即可看到事件流; 签名按空 body 计算, ts 用当前毫秒值)
curl -s "http://{gateway-host}:9100/sdk/v2/debug/events?limit=50" \
-H "Authorization: Bearer {appId}" \
-H "X-Icaiy-Timestamp: {毫秒时间戳}" \
-H "X-Icaiy-Signature: <按 §4.1 以空 body 计算的签名>"成功响应示例(截取):
{
"code": 200,
"message": "操作成功",
"data": {
"events": [
{
"specVersion": 1,
"eventId": "b1c2d3e4-5f60-4a71-8b92-c3d4e5f60718",
"eventType": "message.receipt",
"occurredAt": 1769587200000,
"robot": { "wechatId": "wxid_xxx" },
"payload": { "success": true, "msgId": "1234567890", "peerId": "wxid_peer", "clientCmdId": "cmd-001" }
},
{
"specVersion": 1,
"eventId": "a0b1c2d3-4e5f-4607-8a91-b2c3d4e5f607",
"eventType": "robot.online",
"occurredAt": 1769587140000,
"robot": { "wechatId": "wxid_xxx" },
"payload": { "deviceName": "SDK-Phone-01" }
}
]
}
}6.13 指令回执矩阵与无回执能力披露
6.13.1 指令回执矩阵
45 个端点的逐端点回执已在 §6.1.4 总览表给出,本节按回执形态归类,并给出 task.result 的 taskType 白名单:
task 态(taskId 精确关联,事件回填 clientCmdId):
| 端点 | 回执 | taskType |
|---|---|---|
| 6.2.4 修改机器人资料 | task.result | ModifyProfileTask |
| 6.3.1 发送消息 | message.receipt | 按 MsgId 关联(非 TaskResultNotice 通道) |
| 6.4.1 通过/拒绝好友申请 | task.result | AcceptFriendAddRequestTask(双通道:另有 contact.add/contact.sync 数据流;拒绝操作仅有本回执可感知) |
| 6.4.2 修改好友备注 | task.result | ModifyFriendMemoTask |
| 6.4.3 删除好友 | task.result | DeleteFriendTask |
| 6.4.5 给客户打标签 | task.result | ContactSetLabelTask |
| 6.4.6 批量主动加好友 | task.result | AddFriendsTask(批次级,双通道:成功加上的另有 contact.add/contact.sync;逐条粒度无回执) |
| 6.4.7 搜索账号候选 | task.result | FindContactTask |
| 6.4.8 搜索并直接加好友 | task.result | FindContactTask |
| 6.4.10 僵尸粉重加 | task.result | AddFriendWithSceneTask |
| 6.5.1 群操作(除 create) | task.result | ChatRoomActionTask(双通道:另有 group.changed 数据流;create 建群无任务回执,以 group.add 为准) |
| 6.5.4 群邀请审批 | task.result | ChatRoomInviteApproveTask |
| 6.5.5 扫码进群 | task.result | JoinGroupByQrTask |
| 6.6.2 删除朋友圈 | task.result | DeleteSNSNewsTask |
| 6.6.3 朋友圈点赞 | task.result | CircleLikeTask |
| 6.6.4 朋友圈评论 | task.result | CircleCommentReplyTask(双通道:评论生效另经 circle.list 数据流) |
| 6.6.5 删除朋友圈评论 | task.result | CircleCommentDeleteTask |
| 6.7.1 标记会话已读 | task.result | TriggerMessageReadTask(无关联键,仅机器人维度感知) |
| 6.8.2 添加表情到收藏 | task.result | AddEmojiTask |
| 6.9.1 新建/改名标签 | task.result | ContactLabelTask |
| 6.9.2 删除标签 | task.result | ContactLabelDeleteTask |
event 态(业务事件流感知):6.2.5 robot.qrcode、6.3.2 message.revoked、6.3.3 history.batch、6.3.4 voice.transcript、6.4.4 contact.updated、6.4.9 friends.detect.result(过程 friends.detect.progress)、6.5.1 group.add(create)、6.5.2 group.members、6.5.3 group.qrcode、6.6.1 circle.published(带 clientCmdId 回填)、6.6.6 circle.list、6.7.2 message.received、6.8.1 emoji.list(带 clientCmdId 回填)、6.12.3 logs.pulled。
none 态(仅下发受理,无任何后续回执/事件):6.10.1 sender-config、6.10.3 device-config(通知型)。
同步应答(无回执字段):6.2.1/6.2.2 robots/status、6.2.3 sync(异步受理,数据经事件送达)、6.10.4 device-config GET(同步快照)、6.12.1/6.12.2 死信查看/重放。
taskType 白名单(task.result 事件 taskType 全集,超出此范围的取值不会出现;均为 APP 端任务枚举名):ModifyProfileTask / AcceptFriendAddRequestTask / ModifyFriendMemoTask / DeleteFriendTask / ContactSetLabelTask / AddFriendsTask / FindContactTask / AddFriendWithSceneTask / ChatRoomActionTask / ChatRoomInviteApproveTask / JoinGroupByQrTask / DeleteSNSNewsTask / CircleLikeTask / CircleCommentReplyTask / CircleCommentDeleteTask / TriggerMessageReadTask / AddEmojiTask / ContactLabelTask / ContactLabelDeleteTask。
task.result 事件按 taskId 回填 clientCmdId(§5.5 回执模型)。event 态中仅 circle.published、emoji.list、friends.detect.* 明确携带关联键,其余事件按 §5.6 各事件结构为准。6.13.2 无回执能力的诚实披露(对接前必读)
以下端点没有逐指令任务回执(task.result),对接前请确认业务流程不依赖"指令级成败回执":
| 端点 | 形态 | 结果感知方式 | 对接建议 |
|---|---|---|---|
| 6.10.1 / 6.10.3 通知型配置 | none | 下发成功即生效,无任何后续回执/事件 | 用 §6.10.4 同步查询核对落地快照 |
| 6.5.1 建群(action=create) | event | 成功以 group.add 数据流为准 | 以数据流驱动状态机,勿等待回执 |
| 6.3.2 撤回消息 | event | 结果经 message.revoked 事件感知 | 同上 |
| 6.7.2 订阅开关 | event | 生效证明是 message.received 开始(或停止)到达 | 同上 |
通用建议:这四类端点的 clientCmdId 用于幂等去重与响应回显;事件侧关联能力以各端点说明与 §5.6 事件结构为准。设计上请以数据流为准,勿假设"每条指令必有回执"。
7. 错误码与限流
7.1 v2 错误码表
所有响应 HTTP 恒 200,业务结果以信封 code 为准;错误响应无 data 字段(仅 {code, message})。错误码镜像服务端数字空间,对接网关与服务端可复用同一套解析:
| code | 含义 | message 示例 | 处理建议 |
|---|---|---|---|
| 200 | 成功 | 操作成功 | — |
| 400 | 参数错误 | 字段级文案(如 friendId 必填) | 按 message 修正后重试 |
| 401 | 鉴权失败 | appId无效或客户已停用(Authorization: Bearer {appId}) / 签名校验失败 / 时间戳超出±5分钟有效窗 / 重复请求签名(疑似重放), 请更换时间戳重试 | 核对 §4 四要素;合法超时重试必须更换时间戳重新签名 |
| 409 | 幂等冲突 | clientCmdId 重复(幂等拦截): {clientCmdId} | 该指令已受理过,勿用同一 clientCmdId 重发 |
| 429 | 指令限流 | 指令限流(cmd_qps_limit=10/秒), 请降低调用频率 | 按 message 中配额降频后重试 |
| 2004 | 机器人不存在或无权操作 | 机器人不属于该客户 | 检查 wechatId 是否属于该 appId |
| 2008 | 机器人不在线 | 机器人不在线 | 等 robot.online 后重试;指令端点幂等位已自动释放,可原样重发 |
| 504 | 设备应答超时 | 设备应答超时(5s): 设备 TCP 虽在线但未回传配置, 可能 APP 版本过低或假死, 稍后重试(仅 §6.10.4 同步查询) | 稍后重试;持续出现请检查设备健康 |
| 500 | 网关内部错误 | 服务器内部错误 | 携 X-Icaiy-Request-Id 联系我方运维 |
7.2 限流
| 维度 | 配置项 | 算法 | 超限行为 |
|---|---|---|---|
| 指令下发 | cmd_qps_limit(次/秒,按 appId 配置) | Redis 固定窗口 | 直接拒绝(429),指令未受理,不占幂等位 |
| 事件推送 | event_qps_limit(次/分,按 appId 配置) | 客户级固定窗口 | 指数退避重试,不丢弃(§5.4) |
8. 附录
8.1 消息内容类型
接收侧事件(message.received 等)的 contentType 为设备端枚举名字符串,值域及与发送侧 msgType(§6.3.1)的对照:
| contentType(接收侧枚举名) | 含义 | 对应发送侧 msgType |
|---|---|---|
| Text | 文本 | text |
| Picture | 图片 | image |
| Voice | 语音 | voice |
| Video | 视频 | video |
| Link | 链接卡片 | link |
| NameCard | 名片 | namecard |
| Location | 位置 | location |
| Emoji | 表情 | emoji |
| File | 文件 | file |
| WeApp | 小程序(企微原生 contentType=78 透传) | miniapp |
| ShiPinHao | 视频号 | channel |
| LuckyMoney | 红包 | —(只收不发) |
| ChatRecord | 合并转发的聊天记录 | —(只收不发) |
| System | 系统提示(群管理员变更等) | —(只收不发) |
| UnSupport | 未知类型(content 为原文,日志留痕) | —(只收不发) |
8.2 其他枚举
| 枚举字段 | 出现位置 | 值域 |
|---|---|---|
| gender | 联系人/机器人资料 | Male / Female / UnknownGender(设备端枚举名) |
| contactType | contact.sync / 好友资料 | 1=企业内部联系人(company 纯数字 acctid)/ 2=微信客户(ID 788 前缀)/ 3=企微外部客户(其余,网关推断) |
| receiptMode | 指令响应 data | task(taskId 精确关联)/ event(事件流感知)/ none(仅下发受理) |
| scopes | §6.2.3 数据同步 | groups / contacts / tags / account |
| action | §6.5.1 群操作 | create / rename / notice / invite / kick / transfer_owner / exit |
| msgType(发送侧) | §6.3.1 | text / image / voice / video / link / file / emoji / namecard / location / miniapp / channel |
| attachType | §6.6.1 朋友圈附件 | 0=Link / 2=Picture / 3=ShortVideo / 4=LongVideo / 5=ShiPinHao |
| operation | §6.4.1 好友申请处理 | 1=通过 / 2=拒绝 |
| targetType | §6.4.6 加好友 | wecom(默认)/ wechat / any |
| serving | §6.10.3 msgFilter | 1=上报 / 0=全拦 |
8.3 自测验收清单
对接完成后按序自测,每项通过再进入下一项:
接入与鉴权
- 任一端点返回 200 信封(
code=200, message=操作成功),签名计算与网关一致(错误签名返回 401) - 响应头
X-Icaiy-Request-Id存在,日志可按它串联单次请求 - 同一签名重复发送被 401 重放拦截;更换时间戳重新签名后通过
指令与幂等
- 发文本消息(§6.3.1)→ 收到
message.receipt且回执携带clientCmdId/msgId - 同一
clientCmdId重复提交 → 409;换新 clientCmdId → 正常受理 - 对离线机器人下发指令 → 2008;恢复在线后原样重发成功(幂等位自动释放)
事件接收
- 尚未实现回调端点时:调试页(§3.6)发指令后轮询
GET /sdk/v2/debug/events(§6.12.4)能读到对应回执事件——无代码闭环验证,不依赖回调地址 - 事件接收端点对每批推送返回 HTTP 200 即确认(§5.4);停机 1 分钟再恢复,期间事件补推不丢(at-least-once)
- 同一事件重复推送时业务侧幂等去重,不产生重复业务数据
robot.online/robot.offline与设备真实上下线一致
配置与回执
- 下发
/sender-config(§6.10.1)→ §6.10.4 查询快照与下发一致、senderOn正确 - 下发
/device-config的 msgFilter(§6.10.3)→ 黑名单会话不再产生message.received - 无逐指令回执类端点(建群/撤回/订阅/通知型配置)按 §6.13.2 的数据流方案验证状态机
运维