# 消息推送回复
当你使用微信云托管配置消息推送后,云托管服务就会接收用户从小程序/公众号发来的消息,并以你设定的格式(json或xml)传入你的服务路径中。
你可以在云托管服务中回复特定的json数据来回复。
# 一、被动回复
原理可以参考此文档,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。
需要注意,被动回复只支持公众号,小程序需要使用主动回复。
具体消息格式如下:
# 1. 回复文本消息
{ "ToUserName": "用户OPENID", "FromUserName": "公众号/小程序原始ID", "CreateTime": "发送时间", // 整型,例如:1648014186 "MsgType": "text", "Content": "文本消息" }
# 2. 回复图片消息
{ "ToUserName": "用户OPENID", "FromUserName": "公众号/小程序原始ID", "CreateTime": "发送时间", // 整型,例如:1648014186 "MsgType": "image", "Image": { "MediaId": "素材ID" } }
素材的上传请移步文档最后
# 3. 回复语音消息
{ "ToUserName": "用户OPENID", "FromUserName": "公众号/小程序原始ID", "CreateTime": "发送时间", // 整型,例如:1648014186 "MsgType": "voice", "Voice": { "MediaId": "素材ID" } }
素材的上传请移步文档最后
# 4. 回复视频消息
{ "ToUserName": "用户OPENID", "FromUserName": "公众号/小程序原始ID", "CreateTime": "发送时间", // 整型,例如:1648014186 "MsgType": "video", "Video": { "MediaId": "素材ID", "Title": "视频标题", "Description": "视频描述" } }
素材的上传请移步文档最后
# 5. 回复音乐消息
{ "ToUserName": "用户OPENID", "FromUserName": "公众号/小程序原始ID", "CreateTime": "发送时间", // 整型,例如:1648014186 "MsgType": "music", "Music": { "Title": "音乐标题", "Description": "音乐描述", "MusicUrl": "音乐的链接地址", "HQMusicUrl": "高质量音乐链接,WIFI环境优先使用该链接播放音乐", "ThumbMediaId":"缩略图的媒体id" } }
素材的上传请移步文档最后
# 6. 回复图文消息
{ "ToUserName": "用户OPENID", "FromUserName": "公众号/小程序原始ID", "CreateTime": "发送时间", // 整型,例如:1648014186 "MsgType": "news", "ArticleCount": 2, // 图文消息个数;当用户发送文本、图片、语音、视频、图文、地理位置这六种消息时,开发者只能回复1条图文消息;其余场景最多可回复8条图文消息 "Articles": [{ "Title": "图文标题", "Description": "图文描述", "PicUrl": "图片链接", // 支持JPG、PNG格式,较好的效果为大图360*200,小图200*200 "Url":"点击图文消息跳转链接" },{ "Title": "图文标题", "Description": "图文描述", "PicUrl": "图片链接", // 支持JPG、PNG格式,较好的效果为大图360*200,小图200*200 "Url":"点击图文消息跳转链接" }] }
# 二、主动回复
如果你无法即时回复,需要一段时间后再回复,可以使用接口发起回复。
以下使用建议配合开放接口服务一块使用。
# 接口地址
http://api.weixin.qq.com/cgi-bin/message/custom/send
需要将 /cgi-bin/message/custom/send
加入配置白名单
# 入参数据
- 发送文本
{ "touser":"用户OPENID", "msgtype":"text", "text": { "content":"文本消息" } }
- 发送图片
{ "touser":"用户OPENID", "msgtype":"image", "image": { "media_id":"素材ID" } }
- 发送链接,公众号等同图文,只限1条
{ "touser":"用户OPENID", "msgtype":"link", "link": { "title": "图文标题", "description": "图文描述", "thumb_url": "图片链接", // 支持JPG、PNG格式,较好的效果为大图360*200,小图200*200 "url": "跳转链接" } }
- 发送音乐
{ "touser":"用户OPENID", "msgtype":"music", "music": { "title": "音乐标题", "description": "音乐描述", "music_url": "音乐链接地址", "HQ_music_url": "高清音乐链接地址,用于wifi", "thumb_media_id": "缩略图素材ID" } }
- 发送视频
{ "touser":"用户OPENID", "msgtype":"video", "video": { "media_id": "媒体素材ID", "title": "视频标题", "description": "视频描述" } }
- 发送语音
{ "touser":"用户OPENID", "msgtype":"voice", "voice": { "media_id": "媒体素材ID" } }
- 小程序卡片
{ "touser":"用户OPENID", "msgtype":"miniprogrampage", "miniprogrampage": { "title": "消息标题", "pagepath":"小程序的页面路径", // 跟app.json对齐,支持参数,比如pages/index/index?foo=bar "thumb_media_id": "缩略图素材ID" } }
# 回参数据
{ "errcode": 0, "errmsg": "ok" }
注意,上述的消息类型不是所有都能发送的,不同类型的账号有一定的限制。
比如,公众号类型不可发送「小程序卡片」,小程序类型不可发「视频」、「语音」等,具体都会动态变化,请先验证一下是否可以发送成功。
如果报invalid appid
、 invalid type
信息,就意味着你的appid类型不能发这种消息,这是正常的,不要将其视为错误。

# 三、素材上传
上面发送消息需要的各类素材,可根据业务实际使用情况按需使用对应的接口上传素材,下面以小程序新增图片素材接口为例。公众号可按公众号素材管理相关接口使用即可。
以下使用建议配合开放接口服务一块使用。
# 接口地址
http://api.weixin.qq.com/cgi-bin/media/upload?type=TYPE
需要将 /cgi-bin/media/upload
加入配置白名单
# 入参数据
URL参数中的 type
需要填写自己上传的素材类型值,范围如下:
type值 | 类型 | 描述 |
---|---|---|
image | 图片 | 10M,支持PNG\JPEG\JPG\GIF格式 |
voice | 语音 | 2M,播放长度不超过60s,支持AMR\MP3格式 |
video | 视频 | 10MB,支持MP4格式 |
thumb | 缩略图 | 64KB,支持JPG格式 |
body中传form-data
形式,参数如下:
参数值 | 类型 | 描述 |
---|---|---|
media | File | 媒体文件,有filename、filelength、content-type等信息 |
# 出参数据
正确情况下的返回JSON数据包结果如下:
{ "type":"TYPE", "media_id":"MEDIA_ID", "created_at":123456789 }
参数 | 描述 |
---|---|
type | 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb,主要用于视频与音乐格式的缩略图) |
media_id | 媒体文件上传后,获取标识,3天内有效 |
created_at | 媒体文件上传时间戳 |
错误情况下的返回JSON数据包示例如下:
{ "errcode":40004, "errmsg":"invalid media type" }
# 四、演示DEMO
# 1. 主动回复例子
以下例子使用node.js
来构建公众号主动回复DEMO,其中mediaid自行上传替换,文件夹下共三个文件。
小程序演示效果如下:

index.js
文件,需要注意替换里面所有media_id,以及小程序路径等配置项。
const express = require('express') const request = require('request') const app = express() app.use(express.json()) app.all('/', async (req, res) => { console.log('消息推送', req.body) // 从header中取appid,如果from-appid不存在,则不是资源复用场景,可以直接传空字符串,使用环境所属账号发起云调用 const appid = req.headers['x-wx-from-appid'] || '' const { ToUserName, FromUserName, MsgType, Content, CreateTime } = req.body console.log('推送接收的账号', ToUserName, '创建时间', CreateTime) if (MsgType === 'text') { if (Content === '回复文字') { // 小程序、公众号可用 await sendmess(appid, { touser: FromUserName, msgtype: 'text', text: { content: '这是回复的消息' } }) } else if (Content === '回复图片') { // 小程序、公众号可用 await sendmess(appid, { touser: FromUserName, msgtype: 'image', image: { media_id: 'P-hoCzCgrhBsrvBZIZT3jx1M08WeCCHf-th05M4nac9TQO8XmJc5uc0VloZF7XKI' } }) } else if (Content === '回复语音') { // 仅公众号可用 await sendmess(appid, { touser: FromUserName, msgtype: 'voice', voice: { media_id: '06JVovlqL4v3DJSQTwas1QPIS-nlBlnEFF-rdu03k0dA9a_z6hqel3SCvoYrPZzp' } }) } else if (Content === '回复视频') { // 仅公众号可用 await sendmess(appid, { touser: FromUserName, msgtype: 'video', video: { media_id: 'XrfwjfAMf820PzHu9s5GYsvb3etWmR6sC6tTH2H1b3VPRDedW-4igtt6jqYSBxJ2', title: '微信云托管官方教程', description: '微信官方团队打造,贴近业务场景的实战教学' } }) } else if (Content === '回复音乐') { // 仅公众号可用 await sendmess(appid, { touser: FromUserName, msgtype: 'music', music: { title: 'Relax|今日推荐音乐', description: '每日推荐一个好听的音乐,感谢收听~', music_url: 'https://c.y.qq.com/base/fcgi-bin/u?__=0zVuus4U', HQ_music_url: 'https://c.y.qq.com/base/fcgi-bin/u?__=0zVuus4U', thumb_media_id: 'XrfwjfAMf820PzHu9s5GYgOJbfbnoUucToD7A5HFbBM6_nU6TzR4EGkCFTTHLo0t' } }) } else if (Content === '回复图文') { // 小程序、公众号可用 await sendmess(appid, { touser: FromUserName, msgtype: 'link', link: { title: 'Relax|今日推荐音乐', description: '每日推荐一个好听的音乐,感谢收听~', thumb_url: 'https://y.qq.com/music/photo_new/T002R300x300M000004NEn9X0y2W3u_1.jpg?max_age=2592000', // 支持JPG、PNG格式,较好的效果为大图360*200,小图200*200 url: 'https://c.y.qq.com/base/fcgi-bin/u?__=0zVuus4U' } }) } else if (Content === '回复小程序') { // 仅小程序可用 await sendmess(appid, { touser: FromUserName, msgtype: 'miniprogrampage', miniprogrampage: { title: '小程序卡片标题', pagepath: 'pages/index/index', // 跟app.json对齐,支持参数,比如pages/index/index?foo=bar thumb_media_id: 'XrfwjfAMf820PzHu9s5GYgOJbfbnoUucToD7A5HFbBM6_nU6TzR4EGkCFTTHLo0t' } }) } res.send('success') } else { res.send('success') } }) app.listen(80, function () { console.log('服务启动成功!') }) function sendmess (appid, mess) { return new Promise((resolve, reject) => { request({ method: 'POST', url: `http://api.weixin.qq.com/cgi-bin/message/custom/send?from_appid=${appid}`, body: JSON.stringify(mess) }, function (error, response) { if (error) { console.log('接口返回错误', error) reject(error.toString()) } else { console.log('接口返回内容', response.body) resolve(response.body) } }) }) }
Dockerfile
文件
FROM node:12-slim WORKDIR /usr/src/app COPY package*.json ./ RUN npm config set registry https://mirrors.tencent.com/npm/ RUN npm install COPY . ./ CMD ["node", "index.js"]
package.json
文件
{ "name": "cloudbase-push", "version": "1.0.0", "description": "call push server", "main": "index.js", "scripts": {}, "author": "", "license": "ISC", "dependencies": { "express": "^4.16.4", "request": "^2.88.2" } }
首先,在云托管控制台-云调用中打开开放接口服务,并配置 /cgi-bin/message/custom/send
到微信令牌权限配置。
将上述3个文件组成文件夹,版本创建上传,端口填写 80
,部署发布。
在消息推送配置填写对应的服务,路径填写 /
。
# 2. 被动回复例子
以下例子使用node.js
来构建公众号被动回复DEMO,其中mediaid自行上传替换,文件夹下共三个文件,只适用于公众号。
演示效果如下:

index.js
文件
const express = require('express') const bodyParser = require('body-parser') const PORT = process.env.PORT || 80 const app = express() app.use(bodyParser.raw()) app.use(bodyParser.json({})) app.use(bodyParser.urlencoded({ extended: true })) app.all('/', async (req, res) => { console.log('消息推送', req.body) const { ToUserName, FromUserName, MsgType, Content, CreateTime } = req.body if (MsgType === 'text') { if (Content === '回复文字') { res.send({ ToUserName: FromUserName, FromUserName: ToUserName, CreateTime: CreateTime, MsgType: 'text', Content: '这是回复的消息' }) } else if (Content === '回复图片') { res.send({ ToUserName: FromUserName, FromUserName: ToUserName, CreateTime: CreateTime, MsgType: 'image', Image: { MediaId: 'P-hoCzCgrhBsrvBZIZT3jx1M08WeCCHf-th05M4nac9TQO8XmJc5uc0VloZF7XKI' } }) } else if (Content === '回复语音') { res.send({ ToUserName: FromUserName, FromUserName: ToUserName, CreateTime: CreateTime, MsgType: 'voice', Voice: { MediaId: '06JVovlqL4v3DJSQTwas1QPIS-nlBlnEFF-rdu03k0dA9a_z6hqel3SCvoYrPZzp' } }) } else if (Content === '回复视频') { res.send({ ToUserName: FromUserName, FromUserName: ToUserName, CreateTime: CreateTime, MsgType: 'video', Video: { MediaId: 'XrfwjfAMf820PzHu9s5GYsvb3etWmR6sC6tTH2H1b3VPRDedW-4igtt6jqYSBxJ2', Title: '微信云托管官方教程', Description: '微信官方团队打造,贴近业务场景的实战教学' } }) } else if (Content === '回复音乐') { res.send({ ToUserName: FromUserName, FromUserName: ToUserName, CreateTime: CreateTime, MsgType: 'music', Music: { Title: 'Relax|今日推荐音乐', Description: '每日推荐一个好听的音乐,感谢收听~', MusicUrl: 'https://c.y.qq.com/base/fcgi-bin/u?__=0zVuus4U', HQMusicUrl: 'https://c.y.qq.com/base/fcgi-bin/u?__=0zVuus4U', ThumbMediaId: 'XrfwjfAMf820PzHu9s5GYgOJbfbnoUucToD7A5HFbBM6_nU6TzR4EGkCFTTHLo0t' } }) } else if (Content === '回复图文') { res.send({ ToUserName: FromUserName, FromUserName: ToUserName, CreateTime: CreateTime, MsgType: 'news', ArticleCount: 1, Articles: [{ Title: 'Relax|今日推荐音乐', Description: '每日推荐一个好听的音乐,感谢收听~', PicUrl: 'https://y.qq.com/music/photo_new/T002R300x300M000004NEn9X0y2W3u_1.jpg?max_age=2592000', Url: 'https://c.y.qq.com/base/fcgi-bin/u?__=0zVuus4U' }] }) } else { res.send('success') } } else { res.send('success') } }) app.listen(PORT, function () { console.log(`运行成功,端口:${PORT}`) })
Dockerfile
文件
FROM node:12-slim WORKDIR /usr/src/app COPY package*.json ./ RUN npm config set registry https://mirrors.tencent.com/npm/ RUN npm install COPY . ./ CMD ["node", "index.js"]
package.json
文件
{ "name": "cloudbase-push", "version": "1.0.0", "description": "call push server", "main": "index.js", "scripts": {}, "author": "", "license": "ISC", "dependencies": { "express": "^4.16.4" } }
将3个文件组成文件夹,版本创建上传,端口填写 80
,部署发布。
在消息推送配置填写对应的服务,路径填写 /