发送应用消息
阅读指引:本篇是《开放平台服务端 API 指南》的第 6 部分。接口基础地址、通用约定与各接口请求限制,请先阅读《服务端 API 概览》。
9. 发送应用消息
POST /openapi/v1/messages/send
接口已开放,只接受应用级 Bearer Token 和 application/json 请求。每次请求只能选择一种接收目标类型和一种消息类型;消息发送不额外要求 material:manage,但素材型消息引用的素材必须已经由当前应用上传并处于可用状态。
应用消息与代发消息本接口发送的是“应用以自身身份发送”的应用消息。平台另有受控的代发能力:应用 X 可以代替真实用户 A,向用户 B 或群 C 发送 P2P/Group 消息;向群 C 代发时,用户 A 必须是该群成员。代发的当前边界见第 9.10 节。
9.1 通用请求结构
{
"request_id": "notice_20260826_000001",
"recipient": {
"type": "user",
"ids": ["20001"]
},
"message": {
"type": "text",
"payload": {
"content": "系统维护将在今晚 22:00 开始。"
}
}
}
| 字段 | 必填 | 类型 | 限制与说明 |
|---|---|---|---|
request_id |
是 | string | 本次发送的幂等标识,1~64 个 ASCII 字符;只允许字母、数字、点、下划线、冒号和横线。同一次请求重试必须复用原值和完全相同的目标、消息内容;不得把同一值用于不同消息。 |
recipient |
是 | object | 接收目标。只允许 type 和按目标类型决定是否出现的 ids。 |
message.type |
是 | string | 消息类型:text、file、image、voice、text_card、article_card 或 template_card。 |
message.payload |
是 | object | 必须与 message.type 对应;不接受 null、其他消息类型的字段或任何未声明字段。 |
- 完整 HTTP 请求体最大 64 KiB;超限请求被拒绝。
- JSON 必须只有一个顶层对象;字段名须按本文大小写提交,不得重复声明同一字段,未知字段会导致整个请求无效。
- 初始限流为同一应用与来源 IP 每分钟 60 次;应用应使用指数退避,不能紧密循环重试。
9.2 接收目标与 Scope
recipient.type |
ids |
所需 Scope | 说明 |
|---|---|---|---|
user |
必填,1~100 个 | message:send:user |
用户 ID 使用十进制正整数字符串。每项最长 128 字节;任何重复值(包括数值相同但写法不同的 ID)均拒绝;值须在正 int64 范围内,推荐使用无前导零的标准写法。用户须存在且在发布范围内;默认 target_policy=authorized_users_only 时还须保持有效 OAuth 授权,配置为 reachable_users 时只要求可达且在范围内。 |
org_node |
必填,1~100 个 | message:send:org_node |
组织节点 ID 使用十进制正整数字符串;节点必须 active 且在管理员授权的组织范围内。每个节点固定覆盖自身与当前全部后代。 |
all |
必须省略 | message:send:all |
只表示当前租户全部有效用户;当前还要求发布范围显式包含 user:0。org_node:0、组织根节点或部分用户与组织的组合都不等于 all;即使是空数组也不能提交 ids。 |
group |
必填且只能有 1 个 | group:app |
群 ID 使用十进制正整数字符串。应用必须被群主或管理员添加到该群,且关系当前为 active;应用实时 Scope 和 Token Scope 也都必须包含 group:app。 |
user/org_node/group 的 ids 不接受 JSON 数字、空字符串、零、负数、首尾空白、重复值或超过数量限制的数组。没有权限、不可达或不存在的目标不会因为被提交而获得访问权限。同一组织请求同时选择父节点和其子节点时,服务端只保留父节点覆盖的发送,不让子节点造成重复消息。
9.2.1 应用自身向群发送消息
群应用发送复用本章同一个接口和全部消息体规则,不新增群专用消息接口或消息类型。区别仅在接收目标:recipient.type=group,并且 ids 正好包含一个群 ID。
curl -X POST "${OPENAPI_BASE_URL}/openapi/v1/messages/send" \
-H "Authorization: Bearer ${APP_ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"request_id": "group_notice_20001_20260910_001",
"recipient": {
"type": "group",
"ids": ["20001"]
},
"message": {
"type": "text",
"payload": {
"content": "审批流程已经更新,请及时查看。"
}
}
}'
- 这是应用以自身身份发送的标准群消息,发送者是应用、接收者是群;不是用户授权代发,也不伪装成某个群成员。
- 群成员在群会话中接收该消息;应用自身不因此成为群成员,也不能读取群历史消息。
- 支持的消息类型与应用自身发送保持一致:
text/file/image/voice/text_card/article_card/template_card;当前仍不支持video。 - 文件、图片和语音仍须引用当前应用已上传且可用的
media_id。群目标可发送模板卡片内容,但当前不支持群消息的操作菜单回调;需要动作回调时应使用第 10.9 节支持的服务号通知场景。 - 未绑定、已移除、应用下架、群不可用或暂停的部门群均不会落消息,并作为无效接收目标处理。应用被重新上架后不会自动恢复原群关系。
request_id是幂等标识;同一次重试必须复用原值和完全相同的群及消息内容。
9.3 文本消息 text
{
"type": "text",
"payload": {
"content": "你的订单已经支付成功。"
}
}
| 字段 | 必填 | 限制 |
|---|---|---|
content |
是 | UTF-8 文本,1~2048 字节;不能是纯空白。允许换行、回车和制表符,禁止 NUL 及其他不可见控制字符。 |
9.4 文件、图片和语音消息
三种素材型消息使用相同载荷结构,但 media_id 的素材类型必须和 message.type 完全一致。
{
"type": "image",
"payload": {
"media_id": "media_0123456789abcdef0123456789abcdef"
}
}
message.type |
素材上传类型 | 载荷字段 | 限制 |
|---|---|---|---|
file |
type=file |
media_id,必填 string |
使用文件素材的原文件名、大小、扩展名等已解析信息。 |
image |
type=image |
media_id,必填 string |
只支持素材章节允许的 JPG/JPEG、PNG;应用不提交尺寸或缩略图。 |
voice |
type=voice |
media_id,必填 string |
只支持素材章节允许的 M4A、MP3、WAV;应用不提交时长。 |
media_id长度不超过 64 字节,以media_开头;后续部分只允许 ASCII 字母、数字、下划线和横线。- 素材必须属于当前 Token 对应的应用、已经处理完成且类型匹配。素材不可用、类型错误或引用其他应用的素材均按无效请求处理。
- 不要提交文件名、URL、MD5、大小、扩展名、宽高、缩略图或时长;这些字段会被视为未知字段。
- 当前发送接口不支持
video,即使已经上传视频素材也不能用于本接口。
9.5 文本卡片 text_card
{
"type": "text_card",
"payload": {
"title": "领奖通知",
"description": "恭喜你获得奖品,请在有效期内领取。",
"url": "http://oa.internal/prize",
"buttonName": "更多"
}
}
| 字段 | 必填 | 限制与说明 |
|---|---|---|
title |
是 | 去除首尾空白后非空,最多 128 个 Unicode 字符;禁止控制字符。 |
description |
是 | 纯文本或受限标记文本,包含标记在内最多 512 个 Unicode 字符;必须包含可见文本。 |
url |
是 | 绝对 HTTP/HTTPS URL,最多 2048 字节;点击标题、正文或按钮时打开。 |
buttonName |
否 | 1~4 个文字,禁止纯空白和控制字符;省略时固定显示“详情”。 |
description 可以直接提交纯文本,例如 恭喜你获得奖品。如果需要区分字体颜色,只能使用以下受限标记:
<div class="gray">辅助信息</div>
<div class="normal">正文信息</div>
<div class="highlight">强调信息</div>
<br>
- 只允许
div和br;div必须且只能有一个class属性,值只能是gray/normal/highlight。 div不能嵌套;标签外只能出现空白。禁止链接、图片、脚本、样式、事件属性及其他标签或属性。- 正文中的
&、<、>等特殊字符必须使用 HTML 实体正确转义。非法内容会被拒绝,不会静默删除。
卡片内容安全description 不是通用 HTML 展示区。应用不得把终端用户输入或外部页面片段直接作为标记文本提交;如内容来自用户输入,应按纯文本提交,或先在应用后端生成符合白名单的安全片段。
9.6 图文卡片 article_card
{
"type": "article_card",
"payload": {
"articles": [
{
"title": "中秋节礼品领取",
"description": "今年中秋节公司有豪礼相送",
"url": "http://oa.internal/mid-autumn-gift",
"imageUrl": "http://static.internal/images/mid-autumn-gift.png"
},
{
"title": "领取地点和时间",
"url": "http://oa.internal/mid-autumn-gift/location"
}
]
}
}
| 字段 | 必填 | 限制与说明 |
|---|---|---|
articles |
是 | 数组,1~8 条;不能是 null 或空数组,服务端保持提交顺序。 |
articles[].title |
是 | 去除首尾空白后非空,最多 128 个 UTF-8 字节;禁止控制字符。 |
articles[].description |
否 | 纯文本,最多 512 个 UTF-8 字节;禁止控制字符,不支持 HTML。省略等同于空字符串。 |
articles[].url |
是 | 绝对 HTTP/HTTPS URL,最多 2048 字节;每篇文章使用自己的跳转地址。 |
articles[].imageUrl |
否 | 封面图片的绝对 HTTP/HTTPS URL,最多 2048 字节。省略时使用无图布局。 |
- 第一版只支持 URL 跳转,不支持小程序目标。
- 封面只支持远程
imageUrl,不能使用应用素材media_id。建议提供客户端普遍支持的 JPEG 或 PNG。 - 服务端不会抓取、探测或转存
url/imageUrl;应用负责保证目标在用户网络环境中可访问。
9.7 模板卡片 template_card
模板卡片由客户端原生渲染。当前只开放文本通知型 textNotice 和图文展示型 newsNotice;buttonInteraction、voteInteraction、multipleInteraction 尚未开放。模板卡片沿用第 9.2 节按接收目标确定的消息 Scope,不增加卡片或交互专用 Scope,也不支持用户授权代发接口。
只有操作菜单产生 WebhookactionMenu.actionList[] 的菜单项点击会产生第 10.9 节所述的 message.template_card.action 事件。cardAction、jumpList、引用区、图片区和横向内容中的跳转只在客户端打开目标,不会回传给应用。
文本通知型 textNotice
{
"request_id": "gift_notice_20260907_001",
"recipient": {
"type": "user",
"ids": ["20001"]
},
"message": {
"type": "template_card",
"payload": {
"cardType": "textNotice",
"source": {
"iconUrl": "https://static.example.com/app.png",
"desc": "行政服务",
"descColor": 0
},
"actionMenu": {
"desc": "请选择操作",
"actionList": [
{"text": "确认领取", "key": "confirm"},
{"text": "暂不领取", "key": "decline"}
]
},
"mainTitle": {
"title": "中秋礼品领取通知",
"desc": "请在规定时间内完成确认"
},
"emphasisContent": {
"title": "9月15日",
"desc": "确认截止日期"
},
"subTitleText": "领取时请出示员工工牌。",
"horizontalContentList": [
{"keyName": "领取地点", "value": "行政楼一层"},
{"keyName": "领取说明", "value": "点击访问", "type": 1, "url": "https://oa.example.com/gift"}
],
"jumpList": [
{"type": 1, "title": "查看领取说明", "url": "https://oa.example.com/gift/help"}
],
"cardAction": {
"type": 1,
"url": "https://oa.example.com/gift"
},
"taskId": "gift_2026_user_20001"
}
}
}
| 字段 | 必填 | 限制与说明 |
|---|---|---|
cardType |
是 | 固定为 textNotice。 |
source |
否 | 卡片来源样式,结构见下文。 |
actionMenu |
否 | 卡片右上角操作菜单,结构见下文;填写时 taskId 必填。 |
mainTitle |
条件 | mainTitle.title 与 subTitleText 至少一项非空;标题最多 26 个 Unicode 字符,说明最多 30 个。 |
emphasisContent |
否 | 标题最多 10 个 Unicode 字符,说明最多 15 个;两者不能同时为空。 |
quoteArea |
否 | 引用内容及可选跳转,结构见下文;建议不与 emphasisContent 同时使用。 |
subTitleText |
条件 | 最多 112 个 Unicode 字符。文本通知型不能提交 cardImage/imageTextArea/verticalContentList。 |
horizontalContentList |
否 | 横向键值内容列表,最多 6 项,结构见下文。 |
jumpList |
否 | 跳转指引列表,最多 3 项,结构见下文。 |
cardAction |
是 | 整张卡片的 URL 或小程序跳转,结构见下文;它不产生操作 Webhook。 |
taskId |
条件 | 存在 actionMenu 时必填,格式见下文。 |
图文展示型 newsNotice
{
"type": "template_card",
"payload": {
"cardType": "newsNotice",
"source": {
"iconUrl": "https://static.example.com/news.png",
"desc": "企业资讯",
"descColor": 0
},
"actionMenu": {
"actionList": [
{"text": "感兴趣", "key": "interested"},
{"text": "不再提醒", "key": "mute"}
]
},
"mainTitle": {
"title": "园区开放日活动",
"desc": "欢迎员工及家属报名参加"
},
"cardImage": {
"url": "https://static.example.com/open-day.png",
"aspectRatio": 2.25
},
"verticalContentList": [
{"title": "活动时间", "desc": "9月20日 09:00"},
{"title": "活动地点", "desc": "总部园区"}
],
"cardAction": {
"type": 1,
"url": "https://oa.example.com/open-day"
},
"taskId": "open_day_20260920"
}
}
| 字段 | 必填 | 限制与说明 |
|---|---|---|
cardType |
是 | 固定为 newsNotice。 |
source |
否 | 卡片来源样式,结构见下文。 |
actionMenu |
否 | 卡片右上角操作菜单,结构见下文;填写时 taskId 必填。 |
mainTitle.title |
是 | 最多 26 个 Unicode 字符;mainTitle.desc 最多 30 个。 |
cardImage / imageTextArea |
至少一个 | 必须至少提供一个图片区。cardImage.aspectRatio 省略时为 2.25,提交时必须大于 0。 |
quoteArea |
否 | 引用内容及可选跳转,结构见下文。 |
verticalContentList |
否 | 最多 4 项;每项标题必填且最多 26 个 Unicode 字符,说明最多 50 个。 |
horizontalContentList |
否 | 横向键值内容列表,最多 6 项,结构见下文。 |
jumpList |
否 | 跳转指引列表,最多 3 项,结构见下文。 |
cardAction |
是 | 整张卡片的 URL 或小程序跳转,结构见下文;它不产生操作 Webhook。 |
taskId |
条件 | 存在 actionMenu 时必填,格式见下文。 |
subTitleText / emphasisContent |
禁止 | 图文展示型不能提交这些文本通知型专属字段;即使提交空字符串 subTitleText 也会被拒绝。 |
公共结构、任务与操作菜单
以下结构以字段表中明确引用的卡片类型为准;不能向某种 cardType 附加未声明的结构。
来源 source
| 字段 | 必填 | 限制与说明 |
|---|---|---|
iconUrl |
否 | 来源图标的绝对 HTTP/HTTPS URL;建议使用 72×72 像素方形图片。 |
desc |
否 | 来源说明,最多 20 个 Unicode 字符;它是应用自定义内容,不代表平台认证。 |
descColor |
否 | 整数;0 灰色、1 黑色、2 红色、3 绿色,默认 0。 |
右上角菜单 actionMenu
| 字段 | 必填 | 限制与说明 |
|---|---|---|
desc |
否 | 菜单辅助说明,最多 40 个 Unicode 字符。 |
actionList |
是 | 操作列表,1~3 项;各项 key 不得重复。 |
actionList[].text |
是 | 操作项展示文本,最多 12 个 Unicode 字符,不能是纯空白。 |
actionList[].key |
是 | 1~64 个 ASCII 字节,只允许字母、数字、点、下划线、冒号和横线;菜单事件会原样返回该值。 |
主标题 mainTitle
| 字段 | 必填 | 限制与说明 |
|---|---|---|
title |
条件 | 一级标题,最多 26 个 Unicode 字符;具体必填条件由卡片类型决定。 |
desc |
否 | 标题辅助信息,最多 30 个 Unicode 字符。 |
关键数据 emphasisContent
| 字段 | 必填 | 限制与说明 |
|---|---|---|
title |
条件 | 关键数据文本,最多 10 个 Unicode 字符;与 desc 至少一项非空。 |
desc |
条件 | 关键数据含义,最多 15 个 Unicode 字符;与 title 至少一项非空。 |
引用区域 quoteArea
| 字段 | 必填 | 限制与说明 |
|---|---|---|
type |
否 | 整数;0 无跳转、1 跳转 URL、2 小程序跳转(当前不可用),默认 0。 |
url |
条件 | type=1 时必填,必须是绝对 HTTP/HTTPS URL。 |
appId |
条件 | 当前不可提交;小程序跳转不可用。 |
pagePath |
否 | 当前不可提交;小程序跳转不可用。 |
title |
否 | 引用标题,最多 30 个 Unicode 字符。 |
quoteText |
否 | 引用正文,最多 300 个 Unicode 字符;允许换行、回车和制表符,不支持 HTML。 |
横向内容 horizontalContentList[]
| 字段 | 必填 | 限制与说明 |
|---|---|---|
keyName |
是 | 左侧名称,最多 5 个 Unicode 字符。 |
value |
否 | 右侧内容,最多 30 个 Unicode 字符。 |
type |
否 | 整数;0 普通文本、1 URL、2 附件、3 成员详情,默认 0。 |
url |
条件 | type=1 时必填,必须是绝对 HTTP/HTTPS URL。 |
mediaId |
条件 | type=2 时必填;必须是当前应用可用的文件素材标识。 |
userId |
条件 | type=3 时必填;用户必须处于应用可见范围。 |
跳转指引 jumpList[]
| 字段 | 必填 | 限制与说明 |
|---|---|---|
type |
否 | 整数;0 无跳转、1 URL、2 小程序(当前不可用)、3 智能问答,默认 0。 |
title |
是 | 跳转项标题,最多 30 个 Unicode 字符,不能是纯空白。 |
url |
条件 | type=1 时必填,必须是绝对 HTTP/HTTPS URL。 |
appId |
条件 | 当前不可提交;小程序跳转不可用。 |
pagePath |
否 | 当前不可提交;小程序跳转不可用。 |
question |
条件 | type=3 时必填,最多 300 个 Unicode 字符;允许换行、回车和制表符。 |
卡片整体跳转 cardAction
| 字段 | 必填 | 限制与说明 |
|---|---|---|
type |
是 | 整数;1 跳转 URL、2 小程序跳转(当前不可用)。 |
url |
条件 | type=1 时必填,必须是绝对 HTTP/HTTPS URL。 |
appId |
条件 | 当前不可提交;小程序跳转不可用。 |
pagePath |
否 | 当前不可提交;小程序跳转不可用。 |
通栏图片 cardImage
| 字段 | 必填 | 限制与说明 |
|---|---|---|
url |
是 | 图片的绝对 HTTP/HTTPS URL。 |
aspectRatio |
否 | 有限数值;宽高比,省略时为 2.25,提交时必须大于 0。 |
左图右文 imageTextArea
| 字段 | 必填 | 限制与说明 |
|---|---|---|
type |
否 | 整数;0 无跳转、1 URL、2 小程序(当前不可用),默认 0。 |
url |
条件 | type=1 时必填,必须是绝对 HTTP/HTTPS URL。 |
appId |
条件 | 当前不可提交;小程序跳转不可用。 |
pagePath |
否 | 当前不可提交;小程序跳转不可用。 |
title |
是 | 区域标题,最多 26 个 Unicode 字符,不能是纯空白。 |
desc |
否 | 区域说明,最多 50 个 Unicode 字符。 |
imageUrl |
是 | 左侧图片的绝对 HTTP/HTTPS URL。 |
垂直内容 verticalContentList[]
| 字段 | 必填 | 限制与说明 |
|---|---|---|
title |
是 | 内容标题,最多 26 个 Unicode 字符,不能是纯空白。 |
desc |
否 | 内容说明,最多 50 个 Unicode 字符。 |
任务标识 taskId
存在 actionMenu 时必填;1~128 个 ASCII 字节,只允许字母、数字、下划线、@ 和横线。它是应用提供的业务任务标识,不是发送幂等键;发送幂等仍使用 request_id。
- 请求仍执行严格 JSON 校验:未知字段、字段类型错误、多余的顶层 JSON 值以及与
type不匹配的目标字段都会拒绝整个请求。 - 每个文本字段会去除首尾空白后校验;禁止 NUL、格式控制字符及不允许的不可见控制字符。允许多行的引用正文或问答内容只能使用换行、回车和制表符。
- 同一发送批次的多个接收者可以共享同一个
taskId。应用不得只按taskId去重不同用户的操作,建议业务键至少包含task_id + operator_id + action_key。 - 未配置或未验证 Webhook 不影响卡片发送,但菜单点击事件可能被跳过,且没有补发接口。
小程序跳转当前不可用quoteArea/cardAction/imageTextArea/jumpList 的 type=2 是尚不可用的跳转类型,提交将返回 170410,请使用 URL 跳转。上述字段表中的 appId/pagePath 不应在当前请求中提交。此限制不影响 horizontalContentList[].type=2 的文件附件。
类型相关字段不能混用:无跳转时省略 URL、应用标识和页面路径;URL 跳转只提交 URL;附件只提交 mediaId;成员详情只提交 userId。newsNotice 必须省略 subTitleText,即使空字符串也不可提交。示例中的跳转行为与卡片呈现还需兼容客户端支持。
9.8 URL 与内网地址
卡片 URL 支持公网域名、私有网络域名、私有 IPv4/IPv6,以及部署允许的 HTTP 地址。URL 必须包含协议头,不允许用户名或密码,也不允许 javascript:、data:、file: 等协议。默认禁止 localhost、*.localhost、127.0.0.0/8 和 ::1。
卡片 URL 使用要求发送接口只执行上述 URL 结构、协议和 loopback 校验,卡片跳转 URL 和封面 URL 不要求属于应用配置的可信域名。应用后端必须自行限制可提交的目标域名,不能把终端用户输入直接写入这些字段。
9.9 响应、部分失败与错误码
{
"code": 200,
"message": "ok",
"data": {
"request_id": "notice_20260826_000001",
"message_id": "message_batch_xxx",
"sent_at": 1787727845123,
"invalid_recipients": [
{ "recipient_id": "20002" }
],
"failed_recipients": [
{ "recipient_id": "20003" }
]
}
}
| 响应字段 | JSON 类型 | 出现条件与含义 |
|---|---|---|
code |
integer | 始终返回;成功为 200,调用方必须检查该字段。 |
message |
string | 始终返回的简短结果文本,不应替代 code 做程序判断。 |
data.request_id |
string | 服务端接受并原样返回的请求幂等标识。 |
data.message_id |
string | 成功受理时返回的本次操作消息标识;按不透明字符串处理。 |
data.sent_at |
integer | 成功受理时返回,Unix 毫秒时间戳;应用可自行按目标时区格式化。 |
data.invalid_recipients |
object[] | 不存在、不可达或超出应用授权范围的目标;为空时省略。 |
data.failed_recipients |
object[] | 通过目标校验但实际发送失败的目标;为空时省略。应用可使用新的 request_id 只重试这些目标。 |
*.recipient_id |
string | 请求中对应的原始接收目标 ID。 |
| 业务码 | 含义与处理 |
|---|---|
170410 |
请求结构、接收目标格式、消息字段、内容、素材引用或 URL 无效。 |
170411 |
消息类型尚未开放;模板卡片中也用于拒绝 buttonInteraction/voteInteraction/multipleInteraction。 |
170412 |
全部接收目标无效或应用无权访问。 |
170417 |
消息暂时无法保存或发送;使用原 request_id 和完全相同的请求进行有限次数重试。 |
170105 |
Token 缺少当前 recipient.type 所需 Scope。 |
170104 |
应用 Token 无效、过期、已撤销或 Token 类型错误;重新获取应用 Token。 |
170108 |
同一应用与来源 IP 超过每分钟 60 次;退避到下一个限流窗口。 |
消息接口使用 JSON 业务码表达结果,调用方必须检查 code,不能只根据 HTTP 200 判断成功。invalid_recipients/failed_recipients 为空时会被省略;失败响应通常不包含 data。
9.9.1 请求去重、重试与结果确认
request_id 应在应用内唯一,不能跨不同发送业务复用。应用身份发送的正常去重窗口为 10 分钟:处理中重复提交返回 170419;已有结果时复用发送结果,重复读取不延长窗口;同一标识对应不同目标或内容时返回 170410。权限、目标可用性等条件仍会重新校验,不能要求变更授权后的重试一定返回完全相同的响应。
| 情况 | 建议处理 |
|---|---|
| 200,含失败明细 | 分别处理无效目标与失败目标。去重窗口内原请求重试不会主动重发已返回的失败子集;确认需要重发的失败目标后使用新 request_id。 |
| 170419,request processing | 短暂退避后以同一 request_id 和相同请求重试;不要立刻更换 ID。 |
| 170410 / 170411 / 170412 | 分别检查请求或 ID 冲突、不支持的消息类型、无效接收者;修改业务请求后使用新 ID。 |
| 170417 / 170418 | 发送失败或服务暂不可用;检查返回明细。确认失败后再重发,避免把结果不明误认为一定未发送。 |
| 连接中断、客户端超时、无法解析响应 | 结果不明。在去重窗口内可用原 ID 有限重试,仍需接受重复风险;不要直接以新 ID 重发整个批次。 |
去重是有限保护,不保证消息恰好送达一次。超过窗口或平台去重服务异常时,重复请求可能再次发送;应用应保存自己的业务发送记录,并为重要业务设计重复处理机制。发送成功也不等于用户已收到或已读;当前无应用侧按 request_id 查询最终投递状态的接口。
9.10 用户授权代发消息
POST /openapi/v1/messages/delegate_send
接口已开放。代发表示“用户 A 通过应用 X 向用户 B 或群 C 发送消息”:消息真实发送者是用户 A,应用 X 作为代发应用写入消息展示信息。接口只接受包含 message:delegate:send 的用户级 Bearer Token,不接受应用级 Token。
消息体复用范围request_id、64 KiB 请求体、严格 JSON以及第 9.3~9.6、9.8 节的文本、素材、普通卡片和 URL 规则与 /messages/send 一致。当前代发只支持 text/file/image/voice/text_card/article_card,不支持 video 或 template_card。素材必须属于用户 Token 中绑定的应用 X。
curl -X POST "${OPENAPI_BASE_URL}/openapi/v1/messages/delegate_send" \
-H "Authorization: Bearer ${USER_ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"request_id": "meeting_reminder_20260901_001",
"recipient": {
"type": "user",
"ids": ["20002", "20003"]
},
"message": {
"type": "text",
"payload": {
"content": "会议将在 10 分钟后开始。"
}
}
}'
recipient.type |
ids |
所需 Scope | 约束 |
|---|---|---|---|
user |
必填,1~100 个 | message:delegate:send |
用户 ID 为十进制正整数字符串,格式、长度和去重规则与第 9.2 节一致。不能把当前 Token 对应的用户 A 自己作为目标;无效目标进入 invalid_recipients。 |
group |
必填,1~100 个 | message:delegate:send |
群 ID 为十进制正整数字符串。用户 A 必须是有效群成员,并遵守群状态、全员禁言、成员禁言和消息风控规则;发送失败的群进入 failed_recipients。 |
org_node / all |
不支持 | — | 代发不能用于组织节点或全员群发。 |
9.11 获取代发用户 Token
管理员必须先为应用 X 授予 message:delegate:send,应用再通过第 5 章 H5 OAuth2 或第 6 章 JSSDK getAuthCode,让当前用户 A 授权该 Scope。应用后端使用 authorization code 换取用户 Token,并在代发时直接使用该 Token;请求体不提交用户 A 的 ID。
scope=message:delegate:send
- 用户 A、应用 X 和有效 Scope 均由用户 Token 确定。请求只提交本文列出的字段,不提交或覆盖发送者身份。
- 应用被下架、授权用户被移出应用发布范围、用户 Token 失效、用户撤销该 Scope,或应用当前已不再拥有该 Scope 时,代发都会被拒绝。
- 接收者在与用户 A 的单聊或目标群会话中收到消息,并看到代发应用标识。
9.12 代发响应、时间和限流
HTTP 响应结构与第 9.9 节完全相同。data.sent_at 使用 Unix 毫秒时间戳;invalid_recipients 和 failed_recipients 都回显请求中的原始字符串 ID。
data.message_id的生成和返回规则与/messages/send完全相同:有效发送目标只有一个且成功时返回该消息标识;多个有效目标至少一个成功时返回批次标识;全部失败时不返回。- 同一应用与授权用户每秒最多发起 5 次代发;超限返回
170108。 - 同时保留同一应用与来源 IP 每分钟 60 次的基础限流。
- 至少一个目标成功时
code=200;全部目标无效返回170412;存在有效目标但全部发送失败返回170417。 - 用户 Token 无效或 Token 类型错误返回
170209;缺少message:delegate:send返回170105。
代发重试限制:request_id 仍必填,但用户授权代发不提供第 9.9.1 节的十分钟请求去重保证。相同 ID 重复提交可能产生多条消息;应用须在自己的后端控制并发和重复提交,超时后不要自动无条件重发。
与应用自身发送的身份边界/messages/send 只接受应用级 Token,目标是 user/org_node/all/group,其中 group 必须是应用自身的 active 绑定群;/messages/delegate_send 只接受用户级 Token,目标是 user/group,其中群权限来自授权用户的群成员身份。不要在两个接口之间混用 Token 或群授权模型。