开放接口文档 v1.0
产品介绍 联系商务

企业微信 SCRM SDK 开放接口文档

版本:v2.1(2026-10-28)  适用对象:SDK 客户(集成商)研发与运维
本文档描述贵方系统与我方 SDK 网关之间完整的对接协议,覆盖「设备开通 → 事件接收 → 指令调用」全链路。贵方只需自备安卓设备,使用我方下发的 appId 完成设备登录,即可按本文档独立完成全部搭建,全程无需我方参与现场实施。
契约演进纪律:只加字段、不改语义、不删字段。新增字段不影响已有解析。
v2 为唯一对外协议:全部指令 API 统一为 /sdk/v2 路径,响应信封与错误码以服务端 SaaS API 协议为蓝本({code, message, data} 信封)。

1. 概述

我方提供企业微信 SCRM SDK 网关,让贵方无需部署任何服务即可:

接入形态:

TEXT
企微机器人(我方APP) ──TCP──► 我方网关 ──HTTPS POST──► 贵方回调地址(事件推送)
贵方后端 ──HTTPS POST──► 我方网关指令API(/sdk/v2) ──TCP──► 企微机器人(指令下发)

通信两端皆为 HTTPS + JSON。

1.1 产品形态与三端分工

端部署位置职责
设备端(机器人 APP)贵方自备的安卓手机内嵌企微 hook 感知企微内一切动作,执行网关下发的全部指令(收发消息、建群、加好友、朋友圈、发单等);本地持久化行为配置,断线自愈
SDK 网关(我方)我方云上Netty 长连接枢纽:设备接入认证与归属、指令下发、回执与业务数据上报;翻译为标准事件经 HTTPS 推给贵方;对外提供指令 API / 文件上传 / 运维接口
贵方后端贵方实现 callbackUrl 接收事件(§5)+ 调用指令 API 驱动机器人(§6)
贵方需要准备的全部东西:安卓设备若干台 + 一个 HTTPS 回调端点 + 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 接入地址(随登录下发)
企微账号待托管的机器人企业微信账号(可在设备企微上正常登录)

二、开通步骤(每台设备一次)

  1. 安装企业微信:安装指定版本(5.0.11 / 5.0.12),先不登录。
  2. 安装机器人 APP:安装我方 APK 并授予后台运行 / 自启动 / 电池白名单权限(保活依赖)。
  3. 激活 Xposed 模块:LSPosed → 模块 → 勾选我方 APP → 作用域勾选企业微信 → 重启手机。
  4. 登录机器人 APP:打开 APP,在登录框输入我方下发的 appId(账户) → 点登录。APP 向控制面换取本租户接入配置(网关 TCP 地址 IP[:PORT]、媒体上传地址、转链地址)并自动保存,随后进入常驻后台服务。
  5. 登录企微账号:在设备上的企业微信内登录机器人企微账号(正常企微登录流程,验证码/设备验证按企微提示操作)。APP 会自动拉起并保活企微。
  6. 确认上线:贵方事件端点收到 robot.online 事件(§5.6.5),或调 GET /robots(§6.2.1)看到 online=true —— 该机器人即完成归属、进入可对接状态。此后 APP 断线自动重连(10~40 秒随机间隔)、企微被杀自动拉起,无需人工值守。

三、归属与对应关系(必读)

四、设备状态排障速查

现象排查动作
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 接入三步骤

  1. 从我方管理端获取 appId / appSecret(appSecret 仅此一次展示,请立即保存到贵方密钥库),并登记 callbackUrl。
  2. 按 §2.2 完成设备侧搭建,等待 robot.online 事件(以收到它作为向该机器人发指令的可靠信号,见 §5.6.0)。
  3. 在贵方后端实现一个 HTTPS 端点接收事件推送(见 第 5 章),并调用指令 API 驱动机器人(见 第 6 章)。
测试期可先跳过第 3 步的回调端点实现:用 §3.6 调试页发指令 + §6.12.4 GET /sdk/v2/debug/events 轮询事件,即可无需回调地址完成"开通 → 上线 → 下发 → 回执"全链路验证,联调通过后再实现回调端点切换为实时推送。

2.4 验收清单

见 §8.3 自测验收清单。

3. 调用方式(通用约定)

3.1 服务域名

3.2 公共请求头

指令 API(贵方 → 我方)与事件推送(我方 → 贵方)使用同一套签名,请求都带以下 3 个头:

Header说明
AuthorizationBearer {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 为准:

JSON
{
  "code": 200,
  "message": "操作成功",
  "data": { }
}
字段类型说明
codeint200=受理成功;非 200=失败(见 第 7 章)
messagestring成功为 操作成功;错误时为人话原因(具体到字段)
dataobject业务数据;查询类接口返回数组/对象;错误时缺省

3.4 幂等与回执模型(必读)

所有指令均为异步执行(查询型端点除外),"受理成功"≠"执行成功"。

回执层形式说明
① 受理回执指令 HTTP 同步响应code=200 只代表指令已校验、限流通过并下发到设备;data.receiptEvent/receiptMode 标明后续回执形式
② 任务回执task.result / message.receipt 事件设备端执行有结果语义任务后的成败上报(receiptMode=task)
③ 业务事件回执业务事件流业务生效本身即回执(receiptMode=event):发消息→message.receipt、撤回→message.revoked、建群→group.add、同步→数据事件流
receiptMode含义receiptEvent 示例
task有 taskId 精确关联回执:task.result / message.receipt 事件回传下发时的 taskId 与 clientCmdIdtask.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 事件投递语义

3.6 在线调试(Swagger,免代码测试)

拿到 appId/appSecret 后无需写一行代码,即可在浏览器中完成全部指令 API 的联调测试:

TEXT
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)

调试页使用步骤:

  1. 打开 /swagger-debug.html,在页面顶部「凭证配置」填入 appId 与 appSecret,点保存(存于浏览器 localStorage,仅本机)。
  2. 展开任意端点 → Try it out → 填参数 → Execute——签名由页面内置的 HMAC-SHA256 拦截器自动计算(Base64(HMAC-SHA256(appSecret, timestamp + "\n" + rawBody)),时间戳取本机当前毫秒值),无需手工填任何鉴权头。
  3. 点页面上方「事件观察器」的开始观测:页面每 3 秒轮询 GET /sdk/v2/debug/events,实时展示 robot.online / message.receipt / task.result 等事件(点击行展开完整 JSON)。
  4. 观察响应与参数回显;POST 端点签名含请求体原文,修改参数后直接再次 Execute 即可。

无代码测试闭环(无需回调地址):事件推送(第 5 章)需要贵方拥有公网可达的 HTTPS 端点。为让测试用户在尚未实现回调端点时也能验证完整链路,网关在事件产生时同步写入一份调试事件缓冲(与 callbackUrl 是否配置、回调投递成败完全解耦,内容即实际推送的事件原文),测试用户经 §6.12.4 GET /sdk/v2/debug/events 轮询即可观测事件流。完整闭环:调试页发指令 → 观察器看到对应回执事件 → 验证通过后再实现回调端点接收实时推送。

缓冲保留 24 小时、每客户最多 1000 条(环形挤旧),仅用于测试观测,正式接入后以回调地址实时推送为准;调试端点与全部指令 API 使用同一套鉴权签名,仅能读取本 appId 自己的事件。

注意:

4. 鉴权与签名

4.1 算法

TEXT
signature = Base64( HMAC-SHA256( appSecret, 签名原文 ) )
签名原文   = timestamp + "\n" + rawBody
组成说明
appSecret签名密钥(双向通用:我方推事件用它签名,贵方调指令用它签名)
timestamp即请求头 X-Icaiy-Timestamp 的值
rawBodyHTTP 请求体原字符串;GET 等无 body 时为空串 ""

4.2 计算步骤

以发送一条文本消息(请求体 {"clientCmdId":"c1","friendId":"78813033456789","msgType":"text","text":"你好"})为例:

  1. 取当前毫秒时间戳:1789000000123。
  2. 签名原文 = 1789000000123\n{"clientCmdId":"c1","friendId":"78813033456789","msgType":"text","text":"你好"}(注意时间戳与 body 之间是换行符 \n)。
  3. 用 appSecret 对该原文做 HMAC-SHA256,再做 Base64 编码,即为 X-Icaiy-Signature。

4.3 各语言示例

Java
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
JavaScript
const crypto = require('crypto');
function sign(appSecret, timestamp, rawBody) {
  return crypto.createHmac('sha256', appSecret)
    .update(timestamp + '\n' + rawBody)
    .digest('base64');
}
Python
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 调用示例

Bash
# 发送文本消息
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 请求方式

TEXT
POST {callbackUrl}
Content-Type: application/json
尚未实现回调端点的测试用户:事件产生时会同步写入调试事件缓冲,经 GET /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:

JSON
{
  "specVersion": 1,
  "eventId": "9f86a1c2-3d4e-4f5a-9b8c-1a2b3c4d5e6f",
  "eventType": "message.received",
  "occurredAt": 1760000000123,
  "robot": { "wechatId": "1688857389" },
  "payload": { }
}
字段类型说明
specVersionint契约版本,当前恒为 1
eventIdstring事件唯一 ID(UUID),幂等去重键
eventTypestring事件类型,见 5.6
occurredAtlong网关转发时间戳(毫秒)
robot.wechatIdstring机器人企微账号 ID
payloadobject事件业务数据,统一 schema(见 5.4)

5.4 payload 统一格式(必读)

所有事件的 payload 由网关统一规范化,不透传设备端原始字段名。贵方只需实现一个事件解析函数即可覆盖全部类型。

类型JSON 表现约定
IDstringrobotId / peerId / groupId / msgId / msgSvrId 等一律字符串(企微 ID 超出 JS 安全整数,贵方无需做数字/字符串分支)
数值numbertype / total / timestamp / batteryPercent 等
布尔boolsuccess / selfSent / isOwner 等
枚举string设备端枚举名字符串,如 contentType:"Text"、gender:"Male"(值域见 §8.2 其他枚举)
时间number(秒)统一字段名 timestamp(设备端口径);信封 occurredAt 是毫秒,为网关转发时间,两者语义不同
骨架结构适用事件
列表同步{ 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

5.5 贵方应答与重试

贵方端点必须:

JSON
// 贵方响应回调方(我方)的最小成功报文
{ "code": 0, "message": "ok" }

失败重试:最多投递 5 次(首次 + 4 次重试),指数退避 2s / 4s / 8s / 16s,重试期间 eventId 不变。全部失败进入死信队列(保留 24h),贵方可经运维接口自助查看与重放(见 6.12.1)。

建议实现模式:收到事件先验签 → 落库/入队 → 立即返回 success,业务处理异步做。不要等做完所有业务逻辑再返回,10s 超时会触发重试。

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.updatedrobot.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)
scope 是"触发项"不是"数据过滤项":groups 是全量同步全家桶——只传 ["groups"] 也会收到群+成员+会话+客户+账号全部事件。需要"只要群"请在接收端按 eventType 自行过滤,不要指望 scope 帮你过滤。同一机器人两次同步请间隔 ≥60s(设备端客户/会话同步各有 60s 去重窗口,窗口内第二发会被设备静默跳过)。
为什么没有会话/好友的独立 scope:会话数据由 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.pulledAPP 日志上传完成(fileUrl=日志下载地址)② 调拉取 APP 日志(§6.12.3)后回推
forward.log发单日志(发单/转发消息发送明细,含成功/失败状态)① 设备执行发单/转发后回推明细
贵方实现应忽略未知 eventType(返回 success 即可)。

5.6.2 message.received — 聊天消息

机器人收到或发出的每一条聊天消息(含机器人自己发出的回显)。

字段类型说明
robotIdstring机器人企微 ID
peerIdstring会话对方 ID(单聊=人 ID,群聊=群 ID,无 R: 前缀)
msgIdstring企微本地消息 ID
msgSvrIdstring企微服务端消息 ID(撤回关联键,务必存储)
senderIdstring发送者 ID
selfSentbool是否机器人自己发出(=senderId 为机器人)
contentTypestring内容类型枚举名,如 "Text"(见 §8.1 消息内容类型)
contentstring消息内容(UTF-8 直读):文本=原文、媒体=资源 URL、表情=EmotionInfo JSON
timestampnumber(秒)消息时间
namestring会话对方昵称
avatarstring会话对方头像
talkerIdstring群消息实际发言者 ID(仅群聊携带)
groupName / groupAvatarstring群名 / 群头像(仅群聊携带)
corpNamestring对端企业名称(有则带)
ffAccount / ffNick / customFriendId / userInfo / extstring设备端扩展透传字段(有值才携带;不需要可直接忽略)
atMebool群聊中机器人是否被 @(设备端 hook 结构化解析,被 @成员 / @所有人 均置位)。@其他成员无结构化上报,仅能从 content 文本中的 @昵称 字样辨认
customExtstring引用消息元数据 JSON(引用消息必读,勿忽略):{"quote":1,"refContent":"被引用原文摘要(≤100字)","name":"被引用人昵称","convId":"会话ID","time":被引用时间,"uin","appinfo","ct"}。识别引用消息以 quote=1 为准,勿依赖 contentType;非引用消息不携带本字段
disturbEnablebool对端会话是否免打扰

示例:

JSON
{
  "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 识别):

JSON
{
  "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 — 发消息回执

贵方调用发消息指令后的异步执行结果。

字段类型说明
clientCmdIdstring贵方指令关联 ID(网关回填,与请求一致)
successbool是否发送成功
errorCodestring结果码:Success / NoRight / InvalidParam / InternalError / TargetNotOnline
errorMessagestring失败原因(设备端原文)
robotIdstring机器人企微 ID
peerIdstring接收方 ID
msgIdstring企微本地消息 ID(撤回该消息用此值)
msgSvrIdstring企微服务端消息 ID(入库备查)
timestampnumber(秒)时间

成功示例:

JSON
{
  "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
  }
}

失败示例:

JSON
{
  "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 检测到对方撤回。

字段类型说明
robotIdstring机器人企微 ID
peerIdstring会话 ID(单聊=人 ID,群聊=群 ID)
msgSvrIdstring被撤回消息的企微服务端 ID(关联 message.received 的 msgSvrId)
operatorIdstring撤回操作者 ID(机器人自己撤回时=robotId)
timestampnumber(秒)撤回时间
originalContentstring被撤回消息原文(设备端能取到时返回)

示例:

JSON
{
  "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": "这条消息撤回了"
  }
}
对方撤回为 hook 实时检测;己方撤回成功以本事件为准(失败时设备端无负面回执,兜底策略见 §6.3.2)。

5.6.5 robot.online / robot.offline — 上下线

边缘触发语义(重要):上下线事件仅在状态翻转时推送,不重放。设备在线期间不会重复推 robot.online。因此贵方重启/重新部署后必须先调 GET /robots 快照重建在线初始视图,再靠事件增量维护。

robot.online payload:

字段类型说明
robotIdstring机器人企微 ID
wechatNostring企微号别名(可修改的对外号,通过 §6.2.4 wechatNo 参数变更;改完以最新 account.updated 事件为准)
namestring企微昵称(通过 §6.2.4 nickName 参数变更)
genderstring恒为 "Male"(设备端硬编码)
avatarstring头像 URL
imeistring设备 IMEI
phonestring账号绑定手机号(注册手机,账号固有属性)。注意:不是对外显示的号,改资料接口(§6.2.4)改不了它——企微换绑手机需短信验证,协议层无此能力。若本账号的企微号别名恰好设为手机号样式,本字段与 wechatNo 会同值,但两者是不同字段、变更互不影响
wxVersionstring企微客户端版本
clientVersionstringAPP 客户端版本
serverAddrstringAPP 服务端接入地址(非网络 IP)
companyId / companyNamestring企业 ID / 名称
externalNameTypenumber对外显示名类型: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:

字段类型说明
robotIdstring机器人企微 ID
imeistring设备 IMEI
descstring设备端原文
reasonstring结构化分类,见下表
reason对应场景贵方建议动作
banned封号(desc 含"hook检测到封号"/"账号被冻结")停止该账号全部自动业务,人工处理
logoutIM 主动登出(企微账号在设备退出登录)标记离线,等待重新登录
forced_logout强制下线(人脸识别/登录异常等)标记离线,人工确认后重登
shutdown设备关机标记离线,运维处理
disconnectedTCP 异常断开(网络波动/APP被杀/平台踢线),网关合成标记离线,APP 自动重连

offline 示例:

JSON
{
  "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 实时增量(会话新增/变更,通常单条)。

字段类型说明
robotIdstring机器人企微 ID
itemsarray会话元素数组
items[].idstring会话 ID(单聊=对方 ID,群聊=群 ID,无 R: 前缀)
items[].namestring会话名
items[].avatarstring会话头像
items[].typenumber0=单聊 1=群聊
items[].ownerIdstring群主 ID(群聊且有值才携带)
items[].noticestring群公告(有值才携带)
items[].memberCountnumber群成员数(>0 才携带)
totalnumber本批条数(非全量总数,完成判定靠静默期)
内部群只出现在本事件:group.sync 仅回推客户群(外部群),企业内部群请从本事件(type=1)筛选。

5.6.7 contact.sync — 客户 / 联系人列表(分批)

客户/联系人数据分批回推。source 标识载荷通道,两通道元素结构不同:

字段类型说明
robotIdstring机器人企微 ID
sourcestringfriends=客户/微信好友(2026 通道);contacts=企微联系人(2037 通道,含客户批+内部联系人批)
itemsarray元素数组(结构见下)
totalnumber本批条数

source=friends 元素(客户/微信好友,contact.add/contact.updated 的 item 同此结构):

字段类型说明
idstring客户 ID
nostring客户微信号(有值才携带)
namestring昵称
memostring备注名(企微"备注名"字段,有值才携带)
genderstring性别枚举名
avatarstring头像(有值才携带)
remarkstring备注(有值才携带)
contactTypenumber1=企业内部联系人 / 2=微信客户(ID 788 前缀)/ 3=企微外部客户
tagIdsstring标签 ID(逗号分隔,有值才携带)
phonestring手机号(有值才携带)
descriptionstring描述(有值才携带)
addSourcestring添加来源
addTimenumber添加时间戳(秒,>0 才携带)
openIdstring开放平台 ID(有值才携带)

source=contacts 元素(企微联系人):

字段类型说明
idstring联系人 ID
aliasstring别名(企微号)
namestring昵称
avatarstring头像
phonestring手机号(有值才携带)
descriptionstring描述(有值才携带)
companystring企业名(内部联系人批此字段为纯数字 acctid)
contactTypenumber788 前缀→2;company 纯数字→1;其余→3

示例(friends 批,1 条):

JSON
{
  "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 实时(进群/群资料变化)。

字段类型说明
robotIdstring机器人企微 ID
itemsarray群元素:id/name/avatar/ownerId(有值才携带)/memberCount(>0 才携带)
totalnumber本批条数
groups scope 编排的收尾会有一条 id 为空的占位 group.sync(同步完成标记),入库时请丢弃。

group.members:群成员明细,每群一个事件。双源:② 网关自动调度(收到群列表后自动逐群拉取,1群/秒,无需贵方参与);② 贵方调 §6.5.2 拉取指定群成员 单群刷新。

字段类型说明
robotIdstring机器人企微 ID
groupIdstring群 ID
itemsarray成员元素(见下)
items[].idstring成员 ID
items[].namestring成员昵称(群昵称)
items[].avatarstring头像
items[].typenumber设备端成员类型
items[].isOwnerbool是否群主
items[].isAdminbool是否群管理员
items[].joinTimenumber入群时间戳(秒)
totalnumber本群成员数

示例:

JSON
{
  "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:

字段类型说明
robotIdstring机器人企微 ID
groupIdstring群 ID
changestring变更类型(设备端枚举名,如改群名/公告变更/成员列表变化 Change_MemberList)
contentstring变更内容(新群名/新公告文本)
noticeSenderobject公告发布者(仅公告变更携带):id/name/avatar
noticeTimenumber公告发布时间戳(秒,仅公告变更携带)
建群无任务级回执:建群指令(§6.5.1 action=create)受理后无 task.result,建群成功以 group.add 数据流为准(诚实披露见 §6.13.2)。

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 同构):

字段类型说明
idstring标签 ID(标签管理的删除/打标签都以它为准)
namestring标签名
groupIdstring标签组 ID
type / dataType / sortOrdernumber设备端口径字段

5.6.12 history.batch — 历史消息批次

调 §6.3.3 拉取历史消息 后,设备按 count 分批回推。

字段类型说明
robotIdstring机器人企微 ID
itemsarray消息元素:peerId/contentType(枚举名)/content(UTF-8 直读)/msgId/msgSvrId/selfSent(bool,是否机器人发出)/timestamp(秒)/status(设备端消息状态)
totalnumber本批条数
pagenumber页码

5.6.13 emoji.list / circle.list / circle.published — 表情与朋友圈数据

emoji.list:调 §6.8.1 拉取收藏表情 后回推。items 元素与发消息(§6.3.1)的 emotionInfo 对象同构——拿到列表后原样回传即可发送同款表情。

字段类型说明
robotIdstring机器人企微 ID
taskIdstring关联任务 ID
itemsarray表情元素:url(CDN 直链)/staticUrl(缩略图)/md5/aesKey/desc/groupId/type/width/height/size
totalnumber本批条数

circle.list:调 §6.6.6 拉取朋友圈 后回推(拉自己的/指定好友的都经此通道)。

字段类型说明
robotIdstring机器人企微 ID
itemsarray朋友圈元素(见下)
items[].idstring朋友圈 ID
items[].publishTimenumber发布时间戳(秒)
items[].textstring文字内容(有才携带)
items[].imagesarray图片列表:url/thumb/mediaId/title
items[].linkobject链接附件:同上结构
items[].videoobject视频附件:同上结构
items[].likesarray点赞:friendId/name(有才携带)/timestamp
items[].commentsarray评论:id/content/fromId/fromName/toId/toName/replyCommentId(回复评论时有)/timestamp
total / pagenumber本批条数 / 页码

circle.published:§6.6.1 发布朋友圈 的最终成败回执(taskId 关联 + clientCmdId 回填)。

字段类型说明
robotIdstring机器人企微 ID
taskIdstring任务 ID
successbool发布结果
errorCode / errorMessagestring失败码 / 失败原因(成功时为设备端默认值)
circleIdstring新朋友圈 ID(成功时携带)

5.6.14 task.result / account.updated / device.low_battery

task.result:通用任务回执——有结果语义指令的最终成败通道(哪些指令走此通道见 §6.13 回执矩阵)。

字段类型说明
robotIdstring机器人企微 ID
taskIdstring任务 ID
taskTypestring设备端任务类型枚举名(如 AcceptFriendAddRequestTask、AddFriendWithSceneTask、ChatRoomInviteApproveTask、ContactLabelTask 等)
successbool执行结果
errorCode / errorMessagestring失败码 / 失败原因
clientCmdIdstring贵方指令 ID(网关按 taskId 关联回填;下发时已登记)
datastring任务附数据(部分任务携带,如搜索加好友的候选列表 JSON)

account.updated:已在线机器人账号资料刷新回推(sync scopes=["account"]/groups 编排触发)。payload 同 §5.6.5 robot.online。

device.low_battery:低电量预警(设备仍在线,不触发 robot.offline)。

字段类型说明
robotIdstring机器人企微 ID
imeistring设备 IMEI
descstring设备端原文(low_battery:{百分比})
batteryPercentnumber电量百分比(网关解析)

5.6.15 forward.log — 发单日志

设备执行发单/转发后回推发送明细(含成功/失败状态与转链结果),每条 item = 一次转发发送记录。

字段类型说明
robotIdstring机器人企微 ID
mobilestring设备手机号(有值才携带)
itemsarray明细元素(见下)
totalnumber本批条数

items 元素:

字段类型说明
shillPlanSourceId / shillPlanTargetIdnumber发单计划源 / 目标 ID
sourceRoomIdstring采集群 ID(有值才携带)
collWechatIdstring采集成员 ID(有值才携带)
sourceWechatIdstring源机器人 ID(有值才携带)
senderWechatIdstring执行发送的机器人 ID(有值才携带)
targetRoomIdstring目标群 ID(有值才携带)
forwardMessageIdstring被转发消息 ID
forwardNewMessageIdstring转发后的新消息 ID(有值才携带)
sendStatusstring发送状态(成功/失败)
sendTime / preSendTimestring实际发送时间 / 预定发送时间(有值才携带)
sendMemostring备注(失败原因等,有值才携带)
contentType / content / originalContentstring内容类型 / 内容 / 原始内容(有值才携带)
convertStatusnumber转链状态
convertFailReasonstring转链失败原因(有值才携带)

5.6.16 robot.qrcode — 机器人个人二维码

调 §6.2.5 获取机器人二维码 后回推:APP 拉码 → 上传 → 回推图片 URL。

字段类型说明
robotIdstring机器人企微 ID
successbool拉码结果
qrcodeUrlstring二维码图片 URL(成功时携带)
errorMsgstring失败原因(失败时携带)
clientCmdIdstring贵方指令 ID(网关按机器人维度 FIFO 关联回填)

5.7 媒体上传与文件 URL(媒体文件从哪来)

机器人产生的媒体资源(聊天图片/语音/视频/文件、二维码图片、APP 日志等)的 URL 从哪来?由贵方凭证的 mediaUploadUrl 配置决定,共两条路径:

路径 A:贵方自带存储(mediaUploadUrl 已配置 = 中转模式,推荐)

TEXT
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 的通用约定(三道闸校验、幂等、回执模型),各端点章节只描述差异部分。

通用约定:HTTP 状态码恒为 200,业务成败以 body code 为准(§3.3);指令均为异步执行——code=200(受理成功)≠ 执行成功,最终结果按各端点声明的 receiptEvent 事件回推(§5.6);幂等与回执模型见 §3.4。

6.1 通用约定

6.1.1 统一处理链路与通用错误码

每个端点按以下顺序校验,任一步失败即短路返回(错误响应统一无 data 字段,仅 {code, message}):

步骤校验失败码message 示例
① 鉴权Bearer appId + 时间戳 + 签名(§4)401appId无效或客户已停用(Authorization: Bearer {appId}) / X-Icaiy-Timestamp 缺失或非法 / 时间戳超出±5分钟有效窗 / 签名校验失败 / 重复请求签名(疑似重放), 请更换时间戳重试
② 限流客户级 cmd_qps_limit(次/秒,Redis 固定窗口)429指令限流(cmd_qps_limit=10/秒), 请降低调用频率
③ 归属URL 中 wechatId 必须属于该 appId2004机器人不属于该客户
④ 参数字段校验(错误信息具体到字段)400如 friendId 必填
⑤ 幂等clientCmdId 重复检测(Redis SETNX 24h)409clientCmdId 重复(幂等拦截): {clientCmdId}
⑥ 在线机器人 TCP 是否在线2008机器人不在线
⑦ 应答查询型端点等设备回传(§6.10.4)504设备应答超时(5s): …

其余错误:500 网关内部错误。幂等位自动释放:指令受理后若 TCP 下发失败(2008),网关自动释放本次占用的幂等键,贵方修复后可用同一 clientCmdId 重发。

鉴权失败与重放:带 body 的 POST 请求同一签名只接受一次(±5 分钟窗口内防重放);合法的超时重试请更换时间戳重新签名。GET 请求无副作用,不参与重放拦截。

6.1.2 clientCmdId 与幂等(两档)

贵方自定义唯一指令 ID(≤64 字符建议),贯穿「请求受理 → 回执事件回填 → data 回显」全生命周期(§3.4)。幂等分两档,每端点章节均已标注:

档位语义缺失时
必填档回执需 taskId/clientCmdId 精确关联的指令(发消息/群操作/朋友圈操作/改资料/加表情等)400:clientCmdId 必填(幂等键 + 回执关联键)
可选档受理即走的能力端点(语音转文字/僵尸粉检测与重加/群二维码/标签新建/表情拉取/拉日志)不传直接执行,传入则幂等

6.1.3 回执模型(receiptEvent / receiptMode)

指令端点成功响应的 data 中包含回执声明字段:

字段说明
taskId网关任务 ID(回执事件中回传,用于关联;部分端点无)
receiptEvent后续回执/结果事件名;none 态不返回此字段
receiptModetask=taskId 精确关联(task.result/message.receipt 事件回传 taskId+clientCmdId);event=结果经事件流推送感知(事件携带 taskId 供关联);none=仅下发受理、无任务级回执
clientCmdId请求携带时原样回显

查询型端点(无回执语义)不返回回执字段。

6.1.4 端点总览(45 个)

#端点方法用途幂等回执(receiptEvent / mode)
6.2.1/robotsGET机器人列表快照——(查询)
6.2.2/robots/{wechatId}/statusGET查询在线状态——(查询)
6.2.3/robots/{wechatId}/syncPOST触发数据同步天然幂等—(数据经事件流送达)
6.2.4/robots/{wechatId}/profilePOST修改机器人资料必填task.result / task
6.2.5/robots/{wechatId}/qrcodePOST获取机器人二维码必填robot.qrcode / event
6.3.1/robots/{wechatId}/messagesPOST发送消息必填message.receipt / task
6.3.2/robots/{wechatId}/messages/revokePOST撤回消息必填message.revoked / event
6.3.3/robots/{wechatId}/messages/historyPOST拉取历史消息必填history.batch / event
6.3.4/robots/{wechatId}/messages/voice-to-textPOST语音转文字可选voice.transcript / event
6.4.1/robots/{wechatId}/friends/acceptPOST通过/拒绝好友申请必填task.result + contact.add / task
6.4.2/robots/{wechatId}/friends/remarkPOST修改好友备注必填task.result / task
6.4.3/robots/{wechatId}/friends/deletePOST删除好友必填task.result / task
6.4.4/robots/{wechatId}/contacts/{contactId}/refreshPOST刷新单个客户资料必填contact.updated / event
6.4.5/robots/{wechatId}/friends/labelsPOST给客户打标签必填task.result / task
6.4.6/robots/{wechatId}/friends/addPOST批量主动加好友必填task.result(批次级)+ contact.add / task
6.4.7/robots/{wechatId}/friends/searchPOST搜索账号候选(只搜不加)必填task.result / task
6.4.8/robots/{wechatId}/friends/search-addPOST搜索并直接加好友必填task.result / task
6.4.9/robots/{wechatId}/friends/detectPOST僵尸粉检测可选friends.detect.result / event
6.4.10/robots/{wechatId}/friends/readdPOST僵尸粉重加可选task.result / task
6.5.1/robots/{wechatId}/group-actionsPOST群操作(建群/改名/公告/踢人/退群/转让/邀请)必填task.result(除 create)+ group.add(create)/group.changed / task
6.5.2/robots/{wechatId}/groups/{groupId}/membersPOST拉取指定群成员必填group.members / event
6.5.3/robots/{wechatId}/groups/qrcodePOST获取群二维码可选group.qrcode / event
6.5.4/robots/{wechatId}/group-invites/approvePOST群邀请审批可选task.result / task
6.5.5/robots/{wechatId}/groups/join-by-qrPOST扫码进群可选task.result / task
6.6.1/robots/{wechatId}/circlesPOST发布朋友圈必填circle.published / event(带 clientCmdId)
6.6.2/robots/{wechatId}/circles/deletePOST删除朋友圈必填task.result / task
6.6.3/robots/{wechatId}/circles/likePOST朋友圈点赞必填task.result / task
6.6.4/robots/{wechatId}/circles/commentPOST朋友圈评论必填task.result + circle.list / task
6.6.5/robots/{wechatId}/circles/comment/deletePOST删除朋友圈评论必填task.result / task
6.6.6/robots/{wechatId}/circles/pullPOST拉取朋友圈列表不校验(clientCmdId 仅回显)circle.list / event
6.7.1/robots/{wechatId}/conversations/readPOST标记会话已读必填task.result / task(无 taskId,仅知成败)
6.7.2/robots/{wechatId}/subscribePOST消息订阅开关必填message.received / event(通知型,见端点说明)
6.8.1/robots/{wechatId}/emoji/fetchPOST拉取收藏表情列表可选emoji.list / event
6.8.2/robots/{wechatId}/emoji/addPOST添加表情到收藏必填task.result / task
6.9.1/robots/{wechatId}/labelsPOST新建标签可选task.result / task
6.9.2/robots/{wechatId}/labels/{labelId}DELETE删除标签—task.result / task
6.10.1/robots/{wechatId}/sender-configPOST下发发单配置必填— / none(通知型)
6.10.2(贵方实现的转链服务)—发单转链接口——(设备侧调用)
6.10.3/robots/{wechatId}/device-configPOST下发开关与消息过滤配置必填— / none(通知型)
6.10.4/robots/{wechatId}/device-configGET查询设备当前配置——(同步应答,504 超时)
6.11.1/fileUploadPOST媒体上传中转(设备内部链路)——(§5.7)
6.11.2/fileUploadMd5CheckPOST秒传预检(恒未命中)——(§5.7)
6.12.1/ops/dead-lettersGET查询事件死信——(查询)
6.12.2/ops/dead-letters/replayPOST重放事件死信——(操作型)
6.12.3/robots/{wechatId}/logs/pullPOST拉取 APP 日志可选logs.pulled / event
6.12.4/debug/eventsGET调试事件轮询(无代码测试)——(查询)
无逐指令任务回执的端点:6.10.1/6.10.3 为 none 态;6.5.1 建群、6.3.2 撤回、6.7.2 订阅为 event 态(结果经业务事件感知)。诚实披露与对接建议见 §6.13.2。

6.2 机器人管理

6.2.1 机器人列表快照

用途:拉取贵方名下全部机器人(含被踢设备)的在线状态快照。客户服务启动/重启时必须先调本接口重建初始视图,此后靠 robot.online/robot.offline 事件增量维护——事件为边缘触发(仅状态翻转时推送)且不重放,不拉快照将永远感知不到"一直在线"的设备。

请求:无 body。

BASH
curl -X GET "{网关地址}/sdk/v2/robots" \
  -H "Authorization: Bearer {appId}" \
  -H "X-Icaiy-Timestamp: {毫秒时间戳}" \
  -H "X-Icaiy-Signature: {签名(空body)}"

成功响应:

JSON
{
  "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(毫秒,本次上线翻转时刻;状态未知时缺省)。

JSON
{
  "code": 200, "message": "操作成功",
  "data": {"online": true, "lastOnlineAt": 1760000000000}
}

错误响应:2004(机器人不属于该客户)。回执/事件:无(查询)。

6.2.3 触发数据同步

用途:让机器人重新上报全量/组合数据。scope 为客户自定义组合(全量同步的标准由贵方定义,网关不做固定捆绑),可选值 groups/contacts/tags/account;数组顺序即下发顺序,项间间隔 800ms。

前置条件:机器人在线(2008)。

字段类型必填说明
scopesstring[]是同步项数组,自动去重保序
BASH
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"]}'

成功响应:

JSON
{
  "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);幂等必填档。

字段类型必填说明
clientCmdIdstring是幂等 + 回执关联
nickNamestring否昵称(不能为空串)
wechatNostring否企微号别名(不能为空串)
avatarUrlstring否头像 http/https 直链(APP 自行下载)
externalNameTypeint否对外显示:0=不修改 / 1=实名 / 2=别名
企微约束:切对外显示为别名(2)时账号别名必须非空——机器人当前无别名请随单携带 wechatNo(APP 先改别名再切显示)。

成功响应:

JSON
{
  "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 必填))。

成功响应:

JSON
{
  "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);幂等必填档。

字段类型必填说明
clientCmdIdstring是幂等 + 回执关联
friendIdstring是接收方(单聊=人 ID,群聊=群 ID)
msgTypestring是消息类型,见下表
quoteMsgIdlong否被引用消息的企微本地 ID(= message.received 事件的 msgId);>0 时 APP 构造原生引用区块
…类型专属字段—按类型见下表

msgType 与类型专属字段:

msgType专属字段说明
texttext(必填);mentionIds(可选,string[])文本原文直传;mentionIds 为群内 @ 提及的成员 ID 数组,传 ["0"] 表示 @所有人;可与 quoteMsgId 同条共存(企微原生支持引用+@ 同发)
imageurl(必填)图片 http/https 直链,APP 下载后发送
videourl(必填)视频 URL,APP 下载并生成缩略图
voiceurl(必填)、duration(可选,1~60 秒,缺省 1,超出自动收敛)APP 只认 SILK 直链,贵方 OSS 须自备 SILK 格式;非 SILK 时 message.receipt 回执失败,转码由贵方自理
linkurl(必填);title / description / thumbUrl(可选)链接卡片
fileurl(必填);fileName(可选)文件(fileName 供保存命名)
emojiemotionInfo(必填)收到的表情消息载荷 EmotionInfo 原文(JSON 字符串),或 emoji.list 事件的 item 原样回传
namecardcardId(必填,目标联系人的企微 ID);cardName / cardAvatar / cardCorp / cardJob(可选)名片
locationlatitude + longitude(必填);title / address(可选)位置
miniappappId(必填,兼容小写 appid);title / pagePath(兼容 pagepath)/ appName / username / iconUrl / thumbUrl / description(兼容 desc)(可选)小程序卡片;收到小程序消息原样转发时无需手工改字段名
channelfinderJson(必填)视频号消息 content 原文(JSON 字符串),零处理纯透传

请求示例(文本 + 群内 @):

BASH
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"]}'

成功响应:

JSON
{
  "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);幂等必填档。

字段类型必填说明
clientCmdIdstring是幂等键
friendIdstring是会话对方(单聊=人 ID,群聊=群 ID)
msgIdlong是企微本地消息 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);幂等必填档。

字段类型必填说明
clientCmdIdstring是幂等键
friendIdstring是会话对方(单聊=人 ID,群聊=群 ID)
countint否单批条数,缺省 20,上限 100(超出自动收敛)
startTimelong否起始时间(秒,设备端口径)
endTimelong否锚点 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 直接执行,传入则幂等)。

字段类型必填说明
clientCmdIdstring否幂等键
friendIdstring是好友 ID
msgSvrIdlong是语音消息的服务端消息 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);幂等必填档。

字段类型必填说明
clientCmdIdstring是幂等键
friendIdstring是申请人 ID
operationint否1=通过(默认)/ 2=拒绝;其他值报 400:operation 仅支持 1=通过 / 2=拒绝
ticketstring否申请凭证(可选,APP 端执行只依赖 friendId)
remark / sourcestring/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 批量主动加好友

用途:按手机号列表批量搜索并发送好友申请(批次级指令)。

字段类型必填说明
clientCmdIdstring是幂等键
phonesstring[]是目标手机号数组(缺失报 400:phones 必填(目标手机号数组))
verifyMsgstring否验证消息
remarkstring否通过后备注
targetTypestring否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);幂等可选档;请求体可省略。

字段类型必填说明
clientCmdIdstring否幂等键
onlyCheckbool否true=只检测不删除(默认 false=检测并删除僵尸粉)
skipHourint否跳过 N 小时内活跃的好友(默认 0;负数报 400:skipHour 不能为负(跳过 N 小时内活跃好友))
maxint否单轮检测上限,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);幂等可选档。

字段类型必填说明
clientCmdIdstring否幂等键(传入时 task.result 按 taskId 回填关联)
friendIdstring是好友 ID——取 friends.detect.result 事件的 zombies 项
messagestring是验证消息/打招呼话术

成功响应: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);幂等必填档。

字段类型必填说明
clientCmdIdstring是幂等键
actionstring是create(建群)/ rename(改名)/ notice(群公告)/ invite(邀请入群)/ kick(踢人)/ transfer_owner(转让群主)/ exit(退群);缺失报 400:action 必填(可选: create/rename/notice/invite/kick/transfer_owner/exit),非法报 400:不支持的 action: {action} (可选: …)
groupIdstring视 action群 ID——rename/notice/invite/kick/transfer_owner/exit 必填
memberIdsstring[]视 action成员 ID 数组——create(至少 1 个,即初始成员)与 invite/kick 必填
namestring视 actionrename 必填(新群名);create 可选(新群名)
noticestring视 actionnotice 必填(公告内容)
ownerIdstring视 actiontransfer_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);幂等可选档;请求体可省略。

字段类型必填说明
clientCmdIdstring否幂等键(传入时 task.result 按 taskId 回填关联)
roomIdstring是群 ID(缺失报 400:参数缺失(roomId 必填: 群ID))
msgSvrIdlong是群邀请消息的服务端消息 ID(>0;缺失报 400:参数缺失(msgSvrId 必填: 群邀请消息的服务端消息ID))
msgContentstring是群邀请消息原始 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);幂等必填档。

字段类型必填说明
clientCmdIdstring是幂等键
contentstring二选一文本内容——content 与 attachContent 至少传一个,否则报 400:content(文本) 与 attachContent(附件)至少传一个
attachContentstring二选一附件内容(随 attachType 语义:链接 / 图片 / 视频地址)
attachTypeint否附件类型:0=Link 2=Picture 3=ShortVideo 4=LongVideo 5=ShiPinHao
visibleTypeint否可见范围:0=公开,其余按 APP 可见范围枚举透传(配合 visibleLabels/visibleFriends)
visibleLabelsstring否可见标签(配合可见范围枚举)
visibleFriendsstring否可见好友(配合可见范围枚举)
commentstring否随发评论
sendSlowbool否慢速发送开关(透传 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);幂等必填档。

字段类型必填说明
clientCmdIdstring是幂等键
senderbool是发单开关布尔位(缺失报 400:sender 必填(bool);见下方「sender 字段说明」)
senderConfigstring是发单配置 JSON 字符串(缺失/空报 400:senderConfig 必填(string JSON))

成功响应:data.receiptMode: "none"(无 receiptEvent、无 taskId);clientCmdId 回显。

senderConfig 结构(设备端消费语义):

JSON
{
  "sendInterval": "3000-4000",
  "shillPlanModelList": [
    {
      "shillPlanTargetId": 101,
      "planTargetRoomId": "987654",
      "planTargetSwitch": "1",
      "tbkSwitch": true,
      "convertFailMode": "…",
      "convertFailCustomMsg": "…",
      "planSourceList": [
        {
          "shillPlanSourceId": 1,
          "planSourceRoomId": "123456",
          "planSourceSwitch": "1",
          "planSourceWechatId": "16888xxxx",
          "planSourceMemberId": ""
        }
      ]
    }
  ]
}
字段类型说明
sendIntervalstring随机发送间隔毫秒区间(如 3000-4000=每条间隔 3~4 秒随机);缺省/空回退默认 3000-4000
shillPlanModelListarray发单计划数组(覆盖式)
shillPlanTargetIdint计划 ID(forward.log 转发日志按此关联)
planTargetRoomIdstring转发目标群 ID(可多个)
planTargetSwitchstring"1"=启用该计划
tbkSwitchbool是否走转链(true=转发前调用 §6.10.2 转链接口)
convertFailMode / convertFailCustomMsgstring转链失败处理模式 / 自定义兜底话术
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 → 一律回退直发,不阻塞发单。

请求体(设备 → 贵方):

字段类型说明
wechatIdstring执行发单的机器人 ID
mobilestring设备登录手机号
shillPlanTargetIdint发单计划 ID
contentstring原始消息文本
groupIdListstring[]目标群 ID 数组(裸群 ID)
methodstring固定 batchForwardContentDispose
timestampstring请求时间戳
signstring设备侧固定签名 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 结构:

JSON
{
  "joinRoomSwitch": "1",
  "passFriendSwitch": "1",
  "imgToEmojiSwitch": "0",
  "imgZipSwitch": "1",
  "msgFilter": {
    "serving": 1,
    "blockedIds": "123456,987654",
    "allowedIds": "",
    "keywordEnabled": 1,
    "exactKeywords": "最低价,清仓",
    "fuzzyKeywords": "加微信"
  }
}
字段类型说明
joinRoomSwitchstring自动进群开关("1" 开 / "0" 关)
passFriendSwitchstring自动通过好友开关
imgToEmojiSwitchstring图片转表情开关
imgZipSwitchstring图片压缩开关
msgFilterobject可选——节点缺省=本次不改动过滤策略;节点内按字段级合并(本次出现的字段覆盖、未出现的保留旧值),合并后快照持久化、进程重启自动恢复

msgFilter 字段:

字段类型说明
servingint全局接待门禁:1=上报 / 0=全拦。与 §6.7.2 订阅开关操作同一设备端开关——两条通道谁后下发谁生效,不产生双状态
blockedIdsstring黑名单(不看哪些):逗号分隔会话 ID(单聊=客户 ID、群聊=群 ID,纯数字、无 R: 前缀)
allowedIdsstring白名单(只看哪些):同格式;空=不启用;非空时仅名单内会话实时消息上报
keywordEnabledint关键词过滤开关(1/0)
exactKeywordsstring精确关键词,逗号分隔(整段完全相等才命中)
fuzzyKeywordsstring模糊关键词,逗号分隔(子串包含即命中)

判定优先级(设备端在消息上报唯一入口前置执行):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 秒。

成功响应:

字段类型说明
senderOnstring发单开关:"1"=发单中 / "0"=停发
senderConfigstring设备端当前发单配置快照(原样 JSON 字符串,结构同 §6.10.1)
deviceConfigstring设备端当前开关配置快照(原样 JSON 字符串,结构同 §6.10.3,含 msgFilter 与 serving)

错误响应:2008 机器人不在线;504 设备应答超时(5s): 设备 TCP 虽在线但未回传配置, 可能 APP 版本过低或假死, 稍后重试。

6.11 媒体上传(设备内部链路)

两个端点为设备内部链路(X-Access-Token 设备鉴权,非客户 API),贵方仅在自建对象存储/中转场景需要理解其契约——完整说明(设备令牌、multipart、网关流式中转、响应转译、大小上限)见 §5.7。

编号路径方法用途
6.11.1/fileUploadPOST媒体上传中转:设备 multipart 上传 → 网关按设备归属取 mediaUploadUrl 流式中转 → 客户存储返回 URL → APP 契约响应
6.11.2/fileUploadMd5CheckPOST秒传预检(当前恒返回 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(关闭后本端点恒返回空数组,回调投递不受影响)。

BASH
# 轮询最近 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 计算的签名>"

成功响应示例(截取):

JSON
{
  "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.resultModifyProfileTask
6.3.1 发送消息message.receipt按 MsgId 关联(非 TaskResultNotice 通道)
6.4.1 通过/拒绝好友申请task.resultAcceptFriendAddRequestTask(双通道:另有 contact.add/contact.sync 数据流;拒绝操作仅有本回执可感知)
6.4.2 修改好友备注task.resultModifyFriendMemoTask
6.4.3 删除好友task.resultDeleteFriendTask
6.4.5 给客户打标签task.resultContactSetLabelTask
6.4.6 批量主动加好友task.resultAddFriendsTask(批次级,双通道:成功加上的另有 contact.add/contact.sync;逐条粒度无回执)
6.4.7 搜索账号候选task.resultFindContactTask
6.4.8 搜索并直接加好友task.resultFindContactTask
6.4.10 僵尸粉重加task.resultAddFriendWithSceneTask
6.5.1 群操作(除 create)task.resultChatRoomActionTask(双通道:另有 group.changed 数据流;create 建群无任务回执,以 group.add 为准)
6.5.4 群邀请审批task.resultChatRoomInviteApproveTask
6.5.5 扫码进群task.resultJoinGroupByQrTask
6.6.2 删除朋友圈task.resultDeleteSNSNewsTask
6.6.3 朋友圈点赞task.resultCircleLikeTask
6.6.4 朋友圈评论task.resultCircleCommentReplyTask(双通道:评论生效另经 circle.list 数据流)
6.6.5 删除朋友圈评论task.resultCircleCommentDeleteTask
6.7.1 标记会话已读task.resultTriggerMessageReadTask(无关联键,仅机器人维度感知)
6.8.2 添加表情到收藏task.resultAddEmojiTask
6.9.1 新建/改名标签task.resultContactLabelTask
6.9.2 删除标签task.resultContactLabelDeleteTask

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。

关联键通用规则:指令受理时登记 taskId → clientCmdId 映射;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)
事件侧超限无需贵方处理,但接收端必须保证幂等(at-least-once 语义,§5.4);两个配额的具体数值以客户开通配置为准,429 的 message 会携带当前生效配额。

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 为原文,日志留痕)—(只收不发)
发送侧 msgType 为网关小写字符串枚举;接收侧 contentType 为设备端枚举名(大驼峰),两者经网关归一化(§5.3)后均以字符串下发,无需数字/字符串分支。

8.2 其他枚举

枚举字段出现位置值域
gender联系人/机器人资料Male / Female / UnknownGender(设备端枚举名)
contactTypecontact.sync / 好友资料1=企业内部联系人(company 纯数字 acctid)/ 2=微信客户(ID 788 前缀)/ 3=企微外部客户(其余,网关推断)
receiptMode指令响应 datatask(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.1text / 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 msgFilter1=上报 / 0=全拦

8.3 自测验收清单

对接完成后按序自测,每项通过再进入下一项:

接入与鉴权

指令与幂等

事件接收

配置与回执

运维