微信公众平台已经正式停止模板消息接口服务,原先依赖该能力发送缴费提醒、课程通知、物流状态的公众号运营者和开发者,必须迁移到订阅消息体系。订阅消息分为一次性订阅和长期订阅两种授权模式,二者在用户感知、开发调用和适用类目上有明显区别。理解这些差异并落地对应代码,是这次接口调整中最核心的工作。

一次性订阅与长期订阅的底层机制区别
一次性订阅指的是用户通过点击公众号页面上的授权按钮,明确同意接收一次特定模板的通知。这次授权动作和具体模板编号绑定,服务端只能在用户触发后的七天内,使用该授权下发一条消息。它解决了过去模板消息无需确认即可骚扰用户的问题,把主动权交还给用户。从产品角度看,一次性订阅适合结果明确、无需连续触达的场景,比如用户下单后关心发货,点一次授权收一条发货提醒即可。
长期订阅则完全不同,它允许用户在授权后,开发者在较长周期内多次下发同一类模板消息,不需要每次重新点击。但该能力并未对所有公众号开放,目前仅限政务民生、医疗、交通等少数公共类目申请,普通电商、工具类公众号无法使用。底层上,长期订阅的授权记录写在微信开放平台用户订阅关系表中,并带有类目校验,接口下发时平台会二次核对公众号类目与模板类目是否匹配,不匹配直接返回错误码。
很多团队在迁移时容易混淆二者,以为把原有模板消息代码改成订阅消息接口就能长期推送,结果线上大量丢消息。正确的认知是:没有长期订阅资质,就只能走一次性订阅,且必须在用户点击行为产生的授权内推送。下面的表格简要对比了关键差异。
| 维度 | 一次性订阅 | 长期订阅 |
|---|---|---|
| 授权频率 | 每次推送前需用户点击 | 一次授权周期可用 |
| 开放类目 | 所有公众号 | 仅特定公共类目 |
| 授权有效期 | 7天 | 按类目策略 |
| 下发次数 | 1次 | 多次 |
前端如何拉起一次性订阅授权
在公众号网页中,需要使用微信JS-SDK的requestSubscribeMessage接口拉起授权框。页面必须先通过wx.config注入权限,并在openTagList中声明requestSubscribeMessage。用户点击按钮时,传入模板编号数组,微信会弹出订阅面板,用户勾选允许后,前端会拿到subscribe状态。这里要注意,模板编号需在微信公众后台的订阅消息模板库中申请,且只能使用类目下的模板,不能自定义。
下面是一个最简前端示例,演示如何在用户点击后请求一个发货通知模板的授权。代码中tmplId替换为实际模板编号,成功回调里把授权结果发给自有服务端,由服务端记录这次授权并后续推送。如果用户在面板中点了取消,前端res[tmplId]会是reject,此时不应视为可推送。
// 假设已正确执行 wx.config
document.getElementById('subBtn').addEventListener('click', function () {
var tmplId = 'TEMPLATE_ID_REPLACE';
wx.requestSubscribeMessage({
tmplIds: [tmplId],
success: function (res) {
// res[tmplId] 为 'accept' 表示用户允许
if (res[tmplId] === 'accept') {
// 将授权事件上报给服务端
fetch('https://ipipp.com/api/sub/record', {
method: 'POST',
body: JSON.stringify({ tmpl: tmplId, status: 'accept' })
});
}
},
fail: function (err) {
console.log('订阅拉起失败', err);
}
});
});
有一个常见误区是试图用后端直接调用让用户无感订阅,这不可能实现,因为授权必须由前端用户真实点击产生。另外,iOS和安卓在授权面板样式上略有差异,但接口返回结构一致。测试阶段可以用测试公众号申请模板,避免占用生产模板额度。
服务端调用订阅消息下发接口
当用户完成一次性订阅授权后,服务端需要在七天内调用微信的subscribeMessage.send接口。该接口要求使用公众号的access_token,并且消息体里的touser是用户的openid,template_id是授权时的模板编号,data字段则按模板的关键词填空。如果超过了七天或者用户没授权,接口会返回43101错误,代表用户未订阅。
下面是一段Node.js服务端代码,展示如何下发一条最简单的订阅消息。真实项目里要把access_token缓存起来,不要每次请求都去获取,否则容易触发每日限额。同时建议把下发结果落库,方便排查哪些用户因为授权过期而漏收。
const https = require('https');
const querystring = require('querystring');
function sendSubscribeMessage(accessToken, openid, templateId) {
const postData = JSON.stringify({
touser: openid,
template_id: templateId,
data: {
thing1: { value: '您的订单已发货' },
time2: { value: '2023-08-01 10:00' }
}
});
const options = {
hostname: 'api.weixin.qq.com',
path: '/cgi-bin/message/subscribe/send?access_token=' + accessToken,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData)
}
};
const req = https.request(options, function (res) {
let chunk = '';
res.on('data', function (d) { chunk += d; });
res.on('end', function () {
console.log('微信返回', chunk);
});
});
req.write(postData);
req.end();
}
长期订阅的下发代码与此完全一致,差别仅在于授权来源不同,服务端无需关心用户是一次性还是长期,只要接口不报未订阅错误即可。但要注意,长期订阅类目如果超出范围推送,微信会封禁模板甚至处罚公众号,所以后台应加一层类目校验。此外,订阅消息不支持携带外部跳转链接,只能跳到公众号图文或小程序,这一点和旧模板消息也不同,产品设计时需调整落地页策略。
迁移过程中的避坑与监控建议
从模板消息切到订阅消息,最大的坑是用户习惯改变。过去系统自动发,现在要用户点,转化率会下降。运营上应在关键路径前置引导,比如支付成功页直接放订阅按钮,用利益点如“实时发货通知”提升授权率。技术上,要把原模板消息的触发点全部梳理,改成先查订阅记录再发,没有记录就引导前端授权,而不是盲目调用接口。
监控方面,建议对subscribeMessage.send的返回码做统计,重点关注43101和40003。前者说明授权缺失,后者多为openid错误。可以每天出报表看推送成功率和授权流失环节。对于长期订阅,还要定期核对公众号类目资质是否过期,防止突然不可推送。最后,订阅消息的模板字段有字数限制,比如thing类型只能二十个汉字,超长会被截断,组装data时要做截断保护。
整体来看,这次调整虽然增加了开发量,但让通知更合规。只要分清一次性与长期订阅的边界,把授权动作融入用户流程,服务端按规范调用,就能平滑替代模板消息。后续若微信进一步开放长期订阅类目,再评估升级即可,当前架构应保留扩展位。