企业微信API二次开发:@不到人?可能是这三个字段搞错了
售后群里常出现这种场面:接口返回 code == 0,气泡上也有个蓝色「@李工」,李工的手机却没震动。把文案改成「@李工-北京」再发,有时蓝字都没了。
这不是发送失败,是 展示名和提醒对象用错了字段。客户群指定 @ 做不到,还有另一条更常见的原因:消息根本不是群成员发的,而是官方群机器人通道。
1. 先确认你走的是哪条通道
| 通道 | 能不能进客户群 | @ 能不能产生强提醒 | 发送身份 |
|---|---|---|---|
| 官方群机器人 / 消息推送 | 不能,客户群没有添加入口 | 内部群里 @userid 也经常无提醒 | 机器人 |
| 官方客户群群发 | 能选群 | 不是点名某个人处理 | 要员工确认 |
| 已在群内的企微账号 + 消息接口 | 人在群即可 | 接近手工 @ | 企业微信成员 |
官方机器人走的是群 Webhook 地址,不是群成员身份。客户群没有「添加机器人」,在内部群里即使用了 @userid,对端也常常没有会话强提醒。要在客户群里出现可点击、可提醒的 @,消息必须由 已经在该群里的企业微信账号 发出。
后面所有参数,都建立在「账号级消息接口」上,不再讨论官方机器人。
2. 三种「名字」不要混
客户群里同一个人至少有三套称呼:
| 你在界面上看到的 | 能不能当接口主键 | 改了会怎样 |
|---|---|---|
| 微信昵称 | 不能 | 随时改,群里其他人看到的不一样 |
| 群名片 / 群昵称 | 不能 | 只影响气泡上的字 |
通讯录 userId | 能 | 退群前稳定,提醒认这个 |
还有两个会话 ID 会一起错:
| 字段 | 填什么 | 填错的表现 |
|---|---|---|
guid | 扫码登录后的执行节点 | 掉线、过期,整条发送失败 |
toId | 群聊填 roomId | 填成 userId,消息进私聊,群里没有 @ |
不要用昵称、备注、群名片做主键。重名、改名、一人多个群名片时,都会 @ 错人。
3. 一次指定 @ 要凑齐的参数
统一入口(Host 由你的接入环境决定):
POST /api/qw/doApi
Content-Type: application/json
X-QIWEI-TOKEN: YOUR_TOKEN
import requests
API = "/api/qw/doApi"
HEADERS = {
"X-QIWEI-TOKEN": "YOUR_TOKEN",
"Content-Type": "application/json",
}
def do_api(method, params, timeout=30):
r = requests.post(
API,
headers=HEADERS,
json={"method": method, "params": params},
timeout=timeout,
)
body = r.json()
if body.get("code") != 0:
raise RuntimeError(body)
return body
def send_group_text(guid, room_id, content):
return do_api("/msg/sendText", {
"guid": guid,
"toId": room_id,
"content": content,
"isNoNeedRead": True,
})
def send_group_at(guid, room_id, content, at_user_ids):
"""
at 字段名以你实际消息模块为准:
atlist / mentionList / 混合文本节点里的 mention。
判断标准只有一个:列表里必须是 userId。
"""
return do_api("/msg/sendText", {
"guid": guid,
"toId": room_id,
"content": content,
"atlist": at_user_ids,
"isNoNeedRead": True,
})
混合文本接口在部分环境里 @ 更稳,逻辑不变:toId = roomId,被 @ 人走独立列表,content 里的 @群名片 只负责展示。
一次指定 @ 至少凑齐:
guid- 群
roomId(给toId) - 被 @ 人的
userId(给 @ 列表) content里与当前群名片一致的@名字(给人看)
4. 对照实验:建议按这个顺序做
准备两个测试号 A、B,都在同一客户群。A 是执行账号。
实验 1:只发字符串
send_group_text(guid, room_id, "@李工 看下质检单")
群里有「@李工」四个字,点不出去,B 无提醒。这只是普通文本。code == 0 在这里没有意义,它只说明文本发出去了。
实验 2:文案对、ID 错
send_group_at(guid, room_id, "@李工 看下质检单", at_user_ids=["李工-夜班"])
接口可能直接失败,或发出去仍无提醒。@ 列表不接受名片字符串。
实验 3:文案用群名片,列表用 userId
# 群名片此时是「李工-夜班」
send_group_at(
guid,
room_id,
"@李工-夜班 看下质检单",
at_user_ids=[b_user_id],
)
B 应出现可点蓝字和提醒。把 B 的群名片改成「质检-李工」再发,at 列表不变,提醒仍应打到 B。蓝字文案要跟着新名片改,否则会出现「点进去是对的人,字却对不上」。
实验 4:toId 填成人
send_group_at(guid, b_user_id, "@李工-夜班 看下质检单", at_user_ids=[b_user_id])
消息进私聊。群里没有这条,最像「@ 失败」。
实验 5:@ 两个测试号
atlist 传两个 userId,content 里两个 @群名片 顺序与列表一致(若接口有顺序要求)。只被 @ 的人有提醒,第三人无提醒。
5. userId 从哪取
固定角色(值班、群主、质检对接)
拉该 roomId 的成员列表,落地:
guid
roomId
userId
群名片(可过期)
是否还在群(定时刷新)
发送前用 userId,content 用缓存的群名片。名片刷新失败时,宁可 @ 成功但蓝字略旧,也不要用名片当 ID。
临时点名(客户刚在群里问「质检单」)
从 Webhook 取发送者 ID 和 roomId,不必全量同步:
def on_group_message(event):
room_id = event.get("roomId")
sender_id = event.get("fromUserId")
nick = event.get("fromName") or "群成员"
text = event.get("content") or ""
if "质检单" not in text:
return
send_group_at(
guid=GUID,
room_id=room_id,
content=f"@{nick} 质检单已生成,请查看",
at_user_ids=[sender_id],
)
回调里的展示名只写进 content,不要写进 @ 列表。字段名以实际回调为准,能确定的是:群走 roomId,人走 userId。
成员已退群、执行账号已退群、目标 userId 不属于该 roomId,@ 会无效或接口失败。不要静默当成发送成功。
6. 不要用 @all 代替指定 @
客户群点名是「请这个人处理」。@ 全员容易被屏蔽,也掩盖了「ID 有没有填对」。内部群喊全员、客户群点名值班,不是同一需求。
先把单人 @ 的四个实验做完,再考虑多人列表。不要一上来 @all。
7. 联调验收清单
- 不带 @ 列表的群文本:
code == 0且isSendSuccess == 1,群里能看到 - 带单个
userId:气泡有蓝色可点名字 - 两个测试号:被 @ 的有提醒,未被 @ 的无提醒
- 换未在群内的
userId:失败或 @ 无效,不能静默发进群 toId填userId:确认消息在私聊而不是群- 改群名片后再发:提醒对象不变,只改
content字符串 - 执行账号退群后再发:应失败,而不是发到别的群
日志至少记:guid、roomId、at_user_ids、content 摘要、回包 code、会话里是否有蓝字(人工勾)。出问题先对这张表,不要先改文案。
总结
客户群 @ 不到人,先排除官方机器人通道,再排除 toId 填成了人。文案用群名片,提醒用 userId,群用 roomId。名片改了只改字符串,不要改 ID。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)