导读:本期聚焦于北京网站建设创作的《微信公众号模板消息发送失败怎么办?用户未关注导致发送失败的排查方法》,敬请观看详情。模板消息发送接口返回43101错误码,提示用户未关注公众号,这是微信开发中经常遇到的坑。本文围绕errcode为43101的场景展开,梳理模板消息发送失败的常见原因,包括用户取关后openid仍被缓存、access_token用错appid、测试号与正式号数据混淆等问题,并给出发送前的批量校验方案、错误码捕获处理逻辑以及数据表设计建议,同时提醒模板消息即将被订阅消息替代的迁移注意事项,帮助开发者快速定位问题并建立可靠的失败重试与清理机制。

调用微信公众号模板消息接口时,最让人头疼的报错之一就是errcode为43101,官方给出的解释是“用户未关注公众号”。这个错误看似简单,实际排查起来却常常让人摸不着头脑:明明数据库里存着这个用户的openid,明明昨天还发送成功,怎么今天就未关注了?本文结合实际开发中踩过的坑,把这类问题的排查思路完整梳理一遍。

微信公众号模板消息发送失败怎么办?用户未关注导致发送失败的排查方法

一、先搞清楚43101错误的本质

模板消息的推送对象是“关注了公众号的用户”,这是微信的硬性规则。当接口返回43101时,说明微信服务器判定该openid对应的用户与当前公众号之间不存在关注关系。请求本身格式没问题,access_token也有效,只是目标用户不满足推送条件。

有一个容易被忽视的细节:同一个微信用户在不同公众号下的openid是完全不同的。如果你的系统同时接入了小程序和公众号,用户在小程序侧拿到的openid拿去公众号发模板消息,即使这个用户确实关注了公众号,也会报错。因为openid不通用,必须通过UnionID机制或者分别调用各端的登录接口来获取对应公众号的openid。

下面是一个典型的失败响应:

{
  "errcode": 43101,
  "errmsg": "user refuse to accept the msg hint: [xxxx] "
}

注意errmsg里的"user refuse to accept the msg",有些开发者看到refuse以为是用户拒绝接收,其实取关、关闭公众号消息权限等多种情况都会归到43101这个码上,核心含义就是这条消息发不进去了。

二、四类常见原因逐一排查

第一类:用户已经取关,但业务系统没感知。这是最常见的原因。用户点了取消关注,微信会推送取消关注事件给公众号服务器,但如果你的事件回调处理有漏洞,比如没有更新用户表的关注状态字段,或者消息推送地址配置的域名挂了导致事件丢失,数据库里就会残留一批“僵尸粉丝”。给这些人发消息必然43101。

第二类:openid来源混乱,公众号和小程序搞混了。多端系统里 openid 的管理一定要严格隔离。建议在用户表里明确区分gzh_openid和xcx_openid两个字段,发送模板消息时只用前者。如果确实需要打通两端,应该在用户关注公众号时通过带参二维码或网页授权获取公众号openid并落库。

第三类:access_token对应的appid不对。调用接口时用的access_token如果是用小程序的appid和secret换来的,那么微信会以小程序的身份去查找这个openid,自然找不到关注关系。这类问题报错有时是43101,有时是40003(openid无效),都要检查token的来源。

第四类:测试号与正式号数据串了。测试公众号的模板ID、用户openid和正式环境完全不通用,把测试号的openid拿到正式号下发送,同样会报错。开发阶段建议在配置文件里显式区分环境,避免手滑切换。

三、建立发送前的校验与发送后的处理机制

与其每次发送失败后再补救,不如在架构层面做好两道防线。第一道防线是发送前校验,可以通过公众号的“获取用户信息”接口判断subscribe字段:

// 发送前先校验用户关注状态
async function checkSubscribed(openid, accessToken) {
  const url = 'https://api.weixin.qq.com/cgi-bin/user/info'
    + '?access_token=' + accessToken
    + '&openid=' + openid + '&lang=zh_CN';
  const res = await fetch(url).then(r => r.json());
  // subscribe 为 1 表示已关注,0 表示未关注
  return res.subscribe === 1;
}

不过要注意,如果用户量很大,逐个调用这个接口会消耗不少时间,一般只对重点用户或者在发送失败后触发补偿校验时使用,也可以配合定时任务在低峰期批量刷新关注状态。

第二道防线是发送后的错误码处理。捕获到43101后,绝不能简单地把消息塞回重试队列,因为取关是持久状态,重试多少次都会失败,反而浪费接口调用配额。正确做法是标记该用户为“已取关”,并从后续的推送名单中剔除。示例逻辑如下:

if (result.errcode === 43101) {
  // 用户已取关,标记状态并停止重试
  await db.user.update(
    { openid: targetOpenid },
    { $set: { subscribed: false, unsubscribeTime: new Date() } }
  );
  logger.warn('用户已取关,移出推送名单: ' + targetOpenid);
} else if (result.errcode === 40001 || result.errcode === 42001) {
  // token 失效,刷新后可重试一次
  await refreshTokenAndRetry(message);
} else {
  // 其他错误进入失败队列,记录待人工排查
  await pushToFailedQueue(message, result.errcode);
}

四、正确处理关注与取关事件,从源头杜绝脏数据

要彻底解决43101问题,关键是把关注事件的同步做扎实。公众号服务器配置的消息推送URL中,微信会推送event为subscribe和unsubscribe的事件,务必都处理。很多开发者只处理了subscribe,忽略unsubscribe,时间一长数据库里全是无效粉丝。

<xml>
  <ToUserName><![CDATA[gh_xxxx]]></ToUserName>
  <FromUserName><![CDATA[oXXXX_openid]]></FromUserName>
  <CreateTime>1700000000</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[unsubscribe]]></Event>
</xml>

收到unsubscribe事件后,除了更新数据库的subscribed字段,建议同时清理Redis里缓存的用户信息,避免缓存与库不一致。另外要考虑事件丢失的情况:服务器重启、网络抖动都可能让回调丢失,所以定期(比如每周)调用用户标签或拉取关注者列表接口做一次全量对账,是保证数据准确性的兜底手段。

最后提醒一点,微信官方已经逐步推进模板消息向订阅消息迁移,新接入的服务建议优先评估订阅消息方案。如果存量业务仍在使用模板消息,上述的排查思路和状态管理机制依然适用,只是在迁移时要注意订阅消息需要用户主动授权次数,发送逻辑上会有所不同。把关注状态管理、错误码处理、定期对账这三件事做扎实,43101就不再是让人加班的难题了。

微信公众号模板消息发送失败修改时间:2026-09-03 21:17:06

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260903/49813.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。