消息撤回接口解决的是"发错了能补救"的问题。但撤回有严格的时效限制,理解限制才能用对场景。
一、撤回的基本机制
撤回接口通常需要两个参数:接收方(toUser,好友 wxid 或群 ID)和要撤回的消息标识(原消息发送成功后接口返回的 msgId)。也就是说,撤回依赖发送时保存的 msgId——没存 msgId 就撤不回。
撤回成功后,对方客户端显示"对方撤回了一条消息",消息内容不可见。
二、时效限制——撤回不是随时可以
微信客户端的撤回有时效窗口(通常为两分钟,具体以接口文档为准),超过时效接口返回失败。程序里不要设计"过了一小时再撤回"的逻辑,必然失败。
这意味着撤回能力只适用于"刚发出去就发现错了"的场景。发送时间和撤回请求时间的间隔必须在窗口内。
三、典型使用场景
内容错误:自动回复系统发了错误内容(知识库匹配错、变量填充错),监控发现后立即撤回重发。发错对象:消息路由 bug 导致发到错误的群或人,紧急撤回。重复发送:重试机制产生重复消息,撤回多余的那条。
这些场景的共同点是"秒级发现、秒级撤回"。
四、撤回后的处理
撤回不是终点。撤回后通常要补发正确内容,并在日志里记录:原消息内容是什么、为什么撤回、补发了什么。这条审计链在客服场景里尤其重要。
撤回能力对照
要素 | 说明 |
|---|---|
必要参数 | toUser + 原消息 msgId |
时效窗口 | 发送后短时间内(以文档为准) |
超时效结果 | 接口返回失败 |
对方感知 | 显示"撤回了一条消息" |
前提条件 | 发送时保存了 msgId |
撤回与补发实现
def send_with_recall(to_user, content): """发送消息并保存msgId,支持撤回""" r = api("sendText", {"wId": WID, "toUser": to_user, "content": content}) if r.get("code") == "1000": msg_id = r["data"]["msgId"] db.save("sent_messages", { "msgId": msg_id, "toUser": to_user, "content": content, "sent_at": now() }) return msg_id return None def recall_and_resend(msg_id, to_user, new_content): """撤回错误消息并补发""" original = db.query("sent_messages", msgId=msg_id) if not original: return False, "找不到原消息记录" elapsed = seconds_since(original["sent_at"]) if elapsed > RECALL_WINDOW: # 超过撤回窗口 return False, f"已超时效({elapsed}秒),无法撤回" r = api("revokeMessage", {"wId": WID, "toUser": to_user, "msgId": msg_id}) if r.get("code") != "1000": return False, f"撤回失败:{r.get('code')}" # 记录审计日志 db.save("recall_log", { "msgId": msg_id, "old_content": original["content"], "new_content": new_content, "time": now() }) # 补发 if new_content: send_with_recall(to_user, new_content) return True, "撤回并补发成功"落地建议
用撤回功能的两个前提:发送消息时必须落库 msgId(否则无消息可撤),业务系统要有快速发现错误内容的机制(关键词审计或人工反馈通道)。撤回窗口很短,整个"发现-撤回-补发"链路要在两分钟内走完。接口参数和时效说明以 Eyun 开发文档 为准。