微信回调解析不是难点,难的是把群聊和自发消息判断做对
技术支持 wechatapi.net
做微信机器人这件事,最容易低估的往往不是“接回调”,而是“回调到底该怎么正确解释”。

我最近把微信接到了 OpenClaw,上线前我以为最麻烦的是:
- 回调地址配置
- token 校验
- 发消息接口
真正做起来以后才发现,最容易踩坑的地方其实是:
- 这是不是自己发的消息
- 这是不是群消息
- 群里真正发消息的人到底是谁
如果这三件事处理不对,后面不管你接什么大模型,逻辑都会不稳定。
很多人一开始会写成这样
raw_content = data["Data"]["Content"]["string"]
from_user = data["Data"]["FromUserName"]["string"]
text = raw_content.strip()
sender = from_user
看起来没问题,但在群聊场景下,这种写法经常会直接把群 ID 当成发送人。
为什么会这样
根据实际回调文档,微信群消息并不是一个“字段天然就分好了”的结构。
你至少要分清楚这几个角色:
Wxid:归属微信,也就是当前登录账号Data.FromUserName.string:消息发送方Data.ToUserName.string:消息接收方Data.Content.string:消息内容Data.MsgType:消息类型
重点在这里:
判断是不是自己发的
文档给得很清楚:
可通过消息发送人
$.Data.FromUserName.string与所属微信$.Wxid是否一致进行判断。
所以判断自己发送应该是:
is_self = bool(wxid and from_user == wxid)
判断是不是群消息
文档里也明确区分了两种情况:
- 别人发群:
FromUserName.string以@chatroom结尾 - 自己发群:
ToUserName.string以@chatroom结尾
所以不能只看一个字段。
is_group = from_user.endswith("@chatroom") or to_user.endswith("@chatroom")
判断群里真正是谁发的
这一步最关键。
群聊里,真正的发言人经常藏在 Content.string 前半段:
wxid_xxx:
消息内容
所以还要再拆一次。
我后来用的是这套解析方式
def parse_wechat_payload(data):
wxid = str(data.get("Wxid", "") or "").strip()
msg_data = data.get("Data", {}) or {}
from_user = (msg_data.get("FromUserName", {}) or {}).get("string", "")
to_user = (msg_data.get("ToUserName", {}) or {}).get("string", "")
raw_content = (msg_data.get("Content", {}) or {}).get("string", "")
msg_type = msg_data.get("MsgType")
is_self = bool(wxid and from_user == wxid)
is_group = from_user.endswith("@chatroom") or to_user.endswith("@chatroom")
if from_user.endswith("@chatroom"):
chat_id = from_user
elif to_user.endswith("@chatroom"):
chat_id = to_user
else:
chat_id = from_user if not is_self else to_user
sender_wxid = from_user
actual_text = (raw_content or "").strip()
if is_group and raw_content and ":\n" in raw_content:
possible_sender, possible_text = raw_content.split(":\n", 1)
possible_sender = possible_sender.strip()
if possible_sender.startswith("wxid_") or possible_sender.startswith("v1_") or possible_sender.startswith("gh_"):
sender_wxid = possible_sender
actual_text = possible_text.strip()
return {
"wxid": wxid,
"msg_type": msg_type,
"chat_id": chat_id,
"sender_wxid": sender_wxid,
"actual_text": actual_text,
"is_group": is_group,
"is_self": is_self,
}
这段逻辑看起来比简单脚本复杂不少,但它的好处是:
- 私聊和群聊都能统一得到
chat_id - 群里真正说话的人能拆出来
- 机器人自己发出去的消息能直接过滤
- 后面 session 设计会清晰很多
自发消息不过滤,后果会很严重
这一点一定要单独说。
如果不忽略自己发出的消息,就很容易形成:
- 机器人收到用户消息
- 生成回复
- 把回复发到微信
- 微信又把这条回复回调回来
- 程序把自己的消息再送进 AI
- 最后循环
所以我后面在入口层直接做了:
if parsed.get("is_self"):
logger.info("忽略自己发送的消息: %s", parsed.get("msg_id"))
return {"status": "ignored_self"}
这是一个很小的判断,但实际价值非常大。
群聊和私聊的 session 也不能一样
如果回调解析错了,session 就一定跟着错。
我现在的 session 规则是这样的:
私聊
wechat_dm_{chat_id}
群共享上下文
wechat_group_{chat_id}
群成员独立上下文
wechat_group_{chat_id}_user_{sender_wxid}
代码长这样:
def build_session_id(chat_id: str, sender_wxid: str, is_group: bool, config: dict) -> str:
def norm(s: str) -> str:
return re.sub(r"[^a-zA-Z0-9_-]", "_", str(s or "").strip())
if not is_group:
return f"wechat_dm_{norm(chat_id)}"
if config["GROUP_SESSION_MODE"] == "per_user":
return f"wechat_group_{norm(chat_id)}_user_{norm(sender_wxid)}"
return f"wechat_group_{norm(chat_id)}"
如果 chat_id 和 sender_wxid 本身就没判断对,后面整个上下文系统都会出问题。
为什么我说“回调解析不是难点,判断才是难点”
因为接一个 URL 并不难。
真正难的是:
- 正确理解每个字段的含义
- 不凭想当然做映射
- 在群消息、自发消息、系统消息之间区分清楚
尤其微信这种场景,很多时候“发送人”和“会话所在位置”不是一个概念。
我最后的建议
如果你也在做微信机器人,不管底层是 OpenClaw、LangChain、Dify 还是自己写的 Agent,都建议先把这三件事做扎实:
- 自发消息识别
- 群消息识别
- 群内真实 sender 识别
这三步做对了,后面的 AI 接入才不会乱。
一句话总结:
回调地址只是入口,字段判断才是系统稳定性的底座。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)