微信小程序订阅消息完全指南:从原理到落地的全流程梳理

3 阅读10分钟

本文系统梳理了微信小程序订阅消息的核心概念、对接流程、开发要点和常见踩坑记录,适合产品经理和前后端开发在项目启动前通读一遍,少走弯路。

一、AppSecret 的安全红线

1.1 AppSecret 是什么

AppSecret 是微信小程序的应用密钥,和 AppID 配对使用。它的核心作用是让后端服务器安全地调用微信开放接口,包括:

  • 换取 access_token(几乎所有服务端接口的通行证,有效期 2 小时)
  • 用户登录态换取(code2Session,获取 openid 和 session_key)
  • 发送订阅消息、生成小程序码、内容安全检测等

1.2 绝对不能给第三方

如果你的小程序接入了第三方服务(例如保险、支付、物流等),对方可能会以"需要发送微信消息"为由索要 AppSecret。这是一条安全红线,不能给。

原因很简单:AppSecret 一旦给出去,对方可以冒充你的小程序做任何事——发消息、获取所有用户 openid、消耗接口额度。出了问题,微信追责的是你,不是第三方。

1.3 正确做法:后端中转

第三方系统  →  你的后端 API  →  微信服务器
              (持有 AppSecret)

你提供一个发送消息的中转接口给第三方调用,第三方把用户标识和模板参数传过来,由你的后端调微信接口发送。中转接口的工作量不大(access_token 管理 + 一个 send 接口的封装),1-2 天就能搞定。

二、订阅消息模板:从申请到使用

2.1 模板从哪来

订阅消息模板不是随便写的,必须从微信官方的公共模板库里挑选:

微信公众平台 → 功能 → 订阅消息 → 公共模板库

按行业关键词搜索合适的模板,找到后点"添加"到"我的模板",会得到一个专属的模板 ID

如果公共模板库里没有合适的,可以申请新增模板,提交后微信审核,审核通过后会进入公共模板库(所有人都能用,不是你独有的)。

2.2 模板长什么样

每个模板有固定的字段(关键词),例如:

订单编号:{{character_string1.DATA}}
购买人:  {{name2.DATA}}
购买产品:{{thing3.DATA}}
下单时间:{{time4.DATA}}

每个字段有类型和长度限制(thing 类型最多 20 字符、name 类型最多 10 字符、character_string 类型最多 32 字符等),超长会导致发送失败

2.3 模板 ID 是各小程序独立的

同一个公共模板,不同小程序添加后会生成不同的模板 ID,和各自的 AppID 绑定。类比一下:公共模板库是"户型图样板",你和别的公司都看中了同一个户型,但各自拿到的"房产证号"(模板 ID)不同。

这意味着:

  • 模板 ID 不能跨小程序复用
  • 不要从网上抄别人的模板 ID(用了也发不出去)
  • 字段格式是一致的(因为基于同一个公共模板)

2.4 模板一旦添加不能修改字段

模板添加到"我的模板"后,字段就固定了,没有编辑入口。想加字段只能:

  1. 删掉当前模板(模板 ID 随之作废)
  2. 重新去公共模板库找包含新字段组合的模板
  3. 添加后得到新的模板 ID
  4. 更新前后端所有用到旧模板 ID 的地方

建议:选模板时尽量选字段多一点的,发送时用不到的字段可以不填,但后期想加字段就只能换模板了。

2.5 角色分工

角色职责
业务方(第三方)提出需求:在哪些业务节点发消息、每条消息包含哪些信息
小程序方(你)根据需求去公共模板库找匹配的模板,添加后拿到模板 ID,反馈给业务方
业务方按照模板 ID 和字段格式,在触发业务时调用中转接口传入对应字段值

三、三种订阅类型详解

微信公共模板库中有三个分类:一次性订阅长期订阅会员订阅(Beta)。核心区别是"一次授权能发几条消息"。

3.1 一次性订阅

规则:用户点一次"允许",你只能发一条消息,发完即止。

这是大多数小程序能用的类型。发送额度是纯计数机制:

  • 用户每授权一次某模板 → 该模板额度 +1
  • 每成功发送一条 → 额度 -1
  • 用户连续授权 N 次 → 额度累加到 N
  • 额度和"消息有没有发出去"无关,是独立累加的

常见误解:"用户授权一次,之后想发就发。" ❌ 不行。一次性订阅就是字面意思——一次授权一条消息。

3.2 长期订阅

规则:用户授权一次,可以长期无限次发送同类消息。

但绝大多数小程序用不了。微信只对特定行业类目开放:政务、医疗、交通、金融、教育、公益等。如果你的小程序类目符合条件(例如金融-保险),可以尝试申请。

申请入口:小程序后台 → 功能 → 订阅消息 → 长期订阅 → 申请开通

3.3 会员订阅(Beta)

规则:用户成为小程序"会员"后,在会员有效期内可以长期收到消息。

需要接入微信的会员体系能力,有一定开发成本,且不是所有小程序都能开通。适用于电商会员、知识付费年度会员等场景。

四、授权弹窗的交互细节

4.1 弹窗由微信系统控制

调用 wx.requestSubscribeMessage 后弹出的授权弹窗,其 UI 完全由微信控制:

  • 开发者不能自定义弹窗样式、文案
  • 开关默认关闭,不能设置为默认开启
  • 一次最多传入 3 个模板 ID
wx.requestSubscribeMessage({
  tmplIds: ['模板ID_1', '模板ID_2'],
  success(res) {
    // res['模板ID_1'] === 'accept' 或 'reject'
    // res['模板ID_2'] === 'accept' 或 'reject'
  }
})

4.2 "总是保持以上选择,不再询问"

弹窗底部有这个复选框。一旦用户勾选了:

  • 如果之前选了"允许":之后每次调用都直接 +1 额度,弹窗不再出现(好事)
  • 如果之前选了"拒绝":之后每次调用都直接返回 reject,弹窗也不再出现(坏事)

这个选择是永久生效的,没有过期时间。除非用户自己手动去修改:

小程序右上角「…」→ 设置 → 订阅消息 → 找到对应模板 → 重新切换开关

但绝大多数用户根本不知道有这个入口。所以预防比补救重要——避免高频弹窗导致用户烦躁而勾选"总是拒绝"。

4.3 弹窗触发的硬性要求

wx.requestSubscribeMessage 必须由用户主动点击行为触发,不能在页面加载(onLoad / onShow)时自动调用。这是微信的强制规定。

五、弹窗频率控制

5.1 每次点击都弹

技术上可以做——每次点击按钮时都调用 wx.requestSubscribeMessage。但实际效果取决于用户是否勾选了"总是保持以上选择"。

5.2 每天只弹一次(推荐)

在按钮点击事件中做频率判断:

buyInsurance() {
  const today = new Date().toDateString()
  const lastDate = wx.getStorageSync('lastSubscribeDate')

  if (lastDate === today) {
    // 今天已经弹过了,直接走业务流程
    this.goToBuy()
    return
  }

  wx.requestSubscribeMessage({
    tmplIds: ['模板ID_1', '模板ID_2'],
    success: (res) => {
      // 记录授权结果,通知后端积累发送额度
      const accepted = []
      Object.keys(res).forEach(key => {
        if (res[key] === 'accept') accepted.push(key)
      })
      if (accepted.length > 0) {
        this.reportSubscribeQuota(accepted)
      }
    },
    complete: () => {
      wx.setStorageSync('lastSubscribeDate', today)
      this.goToBuy()
    }
  })
}

如果需要跨设备一致,可以改为后端记录用户的弹窗日期。

5.3 更精细的策略

  • 用户拒绝后 N 天内不再弹
  • 按剩余额度触发(后端配合,没额度时再弹)
  • 在多个入口分散引导(购买页、保单详情页、理赔页),降低单一入口的打扰感

六、消息跳转与 WebView 加载第三方页面

6.1 发送消息时指定跳转路径

调用微信的 subscribeMessage.send 接口时,通过 page 参数控制用户点击消息卡片后跳转到哪个页面:

{
  "touser": "用户openid",
  "template_id": "模板ID",
  "page": "pages/webview/index?url=https%3A%2F%2Fexample.com%2Fh5%2Fdetail%3Fid%3D123",
  "data": {
    "thing1": { "value": "通知标题" },
    "thing2": { "value": "通知内容" }
  }
}

注意事项

  • 路径不带前导斜杠:写 pages/xxx/xxx,不写 /pages/xxx/xxx
  • 路径不带 .vue 后缀
  • 路径必须已在 pages.json 中注册
  • H5 地址必须 URL 编码encodeURIComponent),否则 ?& 会被错误解析为小程序页面的参数

6.2 目标页面接收参数并加载 WebView

<template>
  <web-view v-if="h5Url" :src="h5Url"></web-view>
</template>

<script>
export default {
  data() {
    return { h5Url: '' }
  },
  onLoad(options) {
    if (options.url) {
      let url = options.url
      if (url.includes('%')) {
        url = decodeURIComponent(url)
      }
      this.h5Url = url
    }
  }
}
</script>

6.3 必须配置业务域名

<web-view> 能加载的 URL,其域名必须已在微信公众平台添加到业务域名白名单

微信公众平台 → 开发 → 开发管理 → 开发设置 → 业务域名

配置要求:

  • 域名必须是 HTTPS
  • 需要下载校验文件放到目标域名的根目录
  • 如果是第三方的域名,需要对方配合放校验文件
  • 未配置的域名,WebView 会空白或报错

6.4 和第三方对接跳转路径

给第三方提供跳转路径时,说清楚三件事:

  1. 路径格式pages/webview/index?url={编码后的H5地址}
  2. 编码规则:url 参数的值必须做一次 encodeURIComponent
  3. 谁负责编码:建议由你的后端统一负责编码和拼接,第三方只需传原始 H5 地址

七、常见踩坑清单

说明
字段长度超限微信对每个字段类型有严格字符数限制,超长发送直接失败。中转接口最好做截断或校验。
订阅次数误解一次性订阅不是"订阅一次永久能发"。要让业务方理解这一点。
内容不能营销订阅消息不能发广告,只能发服务通知,违规会被封禁模板甚至小程序。
测试/正式模板 ID 搞混开发环境和正式环境用不同模板 ID 时,切换环境要同步更新。
page 路径写错消息能发出去但用户点击后跳转失败,建议发送前做路径白名单校验。
业务域名未配置WebView 最常见的问题,表现为空白页或报错。
H5 地址参数丢失不编码就传,H5 端拿到的参数会不完整。
WebView 限制个人类型小程序不能用 <web-view>;WebView 会占满整个屏幕;H5 要调小程序能力需引入 JSSDK。
"总是拒绝"后无法弹窗用户勾选"总是保持以上选择"并拒绝后,除非用户手动去设置里改,否则永远无法再弹窗。
后端不知道剩余额度微信不提供查询某个用户剩余额度的接口,需要自己在后端维护计数。

八、完整对接流程一览

┌─────────────── 准备阶段 ───────────────┐
│                                         │
│  1. 业务方提出通知需求(哪些节点、什么内容)│
│  2. 你在小程序后台的公共模板库选择模板     │
│  3. 添加到"我的模板",拿到模板 ID         │
│  4. 把模板 ID + 字段规范发给业务方        │
│  5. 双方约定中转接口协议                  │
│                                         │
├─────────────── 用户使用 ───────────────┤
│                                         │
│  6. 用户点击按钮 → 前端调用              │
│     wx.requestSubscribeMessage          │
│  7. 用户在弹窗中授权                     │
│  8. 前端通知后端记录授权额度              │
│                                         │
├─────────────── 消息触发 ───────────────┤
│                                         │
│  9. 业务方调用你的中转接口(传入用户标识、 │
│     模板类型、字段值、跳转的 H5 地址)    │
│ 10. 你的后端校验请求 → 编码拼接跳转路径   │
│     → 换 access_token → 调微信           │
│     subscribeMessage.send               │
│ 11. 用户在微信"服务通知"中收到消息卡片   │
│ 12. 点击卡片 → 跳转小程序指定页面        │
│     → WebView 加载第三方 H5              │
│                                         │
└─────────────────────────────────────────┘

九、关键结论速查

  • AppSecret 绝对不能给第三方,用中转接口解决
  • 模板 ID 各小程序独立,不能跨小程序复用
  • 模板字段不可修改,选模板时多选几个字段留余量
  • 一次性订阅 = 一次授权一条额度,额度可累加
  • 弹窗开关默认关闭,微信强制行为,无法修改
  • "总是保持以上选择"永久生效,用户拒绝后几乎不可逆
  • 弹窗频率可以自行控制,但弹不弹得出来取决于用户之前的选择
  • H5 跳转地址必须 URL 编码,域名必须加入业务域名白名单