UnifyPort 媒体发送:文件 URL 与送达问题排查
通过 UnifyPort 发送图片、视频、音频或文档时,使用 POST /v1/messages,设置渠道支持的 message.type,并在 message.url、message.file_url 或 message.file_key 中提供至少一个非空来源。URL 必须是完整的 HTTP(S) 地址。本地文件名不是可访问的媒体 URL,响应中的 status: accepted 也不代表收件人已收到文件。
先记住四个边界
- 先检查消息账号所属渠道,再开放媒体类型选项。
- 优先提供一个明确、可访问的 URL,不猜测多个来源字段的优先级。
- 出站
message与入站data.message.attachments[]不是同一请求结构。 - 文件访问、请求校验、账号连接和最终送达应分别排查。
先确定使用哪套发送契约
UnifyPort 的非官方接口遵循媒体发送文档,不是 Telegram Bot API 或 LINE Messaging API。例如,Telegram 文件发送文档描述了其原生方法使用的文件标识、HTTP URL 和 multipart 上传;这不能证明 UnifyPort 将 Telegram file_id 作为 file_key 接受,也不能证明两者支持相同的上传请求。
| 字段 | UnifyPort 文档明确的含义 |
|---|---|
message.type | 媒体类型包括 image、video、audio、document、file,仍须检查渠道支持情况 |
message.url 或 message.file_url | 非空、完整的 HTTP(S) 来源地址 |
message.file_key | 已记录的另一种来源方式,不意味着可以自造 key 或上传端点 |
message.caption | 图片、视频、文档和文件可选的说明文字 |
provider_data.seconds | WhatsApp 音频和视频可选的非负整数时长,单位为秒 |
WhatsApp 视频的时长取值范围为 0–4294967295;provider_data.waveform 仍仅用于音频。不要把入站 duration_ms 原样填进秒数字段。不需要时长时,宁可省略,也不要猜值。
当前消息能力矩阵未映射 TikTok 音频及文档/文件发送,将 X 音频标为部分支持,并分别列出 whatsapp-protocol 与 whatsapp。统一端点不等于每个渠道拥有相同能力和限制。面向东南亚的跨境团队也应按实际渠道控制发送界面。
构造请求前,先准备文件
以下是应用设计建议,不是新增的平台保证:
- 确认操作员有权使用所选账号向目标会话发送内容。群聊应使用会话 ID 和类型,而不是消息作者的 ID。
- 分别检查授权状态和
runtime_status;已授权不等于连接在线。 - 将正确的文件放在受控且可访问的来源中。浏览器登录后能打开的页面,不足以证明发送服务能取得文件。
- 从独立服务器环境、不携带浏览器 Cookie 测试读取,检查返回的是真实媒体,而不是 HTML 登录页或错误页。
- 使用限时 URL 时,为预计排队和读取时间安排有效期。发送文档未承诺统一的抓取截止时间;预检成功也不保证稍后仍可访问。
建议使用 HTTPS、最小访问范围和应用批准的存储来源。不要把签名查询参数或凭据写入日志。这些是安全建议,不代表 UnifyPort 存在某种未公开的网络过滤机制。
构造基于 URL 的媒体请求
下面的 JavaScript 函数使用应用已选定的账号、会话和获准媒体 URL 生成请求体。它不上传文件,也不验证远端实际可访问性。调用前仍须完成渠道能力和操作权限检查。
function buildMediaRequest({ accountId, conversation, type, url, caption }) {
const types = new Set(['image', 'video', 'audio', 'document', 'file']);
if (!accountId || !conversation?.id || !conversation?.type) {
throw new Error('Account and conversation are required');
}
if (!types.has(type)) throw new Error('Unsupported media type');
const source = new URL(url);
if (!['http:', 'https:'].includes(source.protocol) ||
source.username || source.password) {
throw new Error('Use an approved HTTP(S) media source');
}
const message = { type, url: source.href };
if (caption !== undefined) {
if (type === 'audio' || typeof caption !== 'string') {
throw new Error('Caption is not valid for this request');
}
message.caption = caption;
}
return {
account_id: accountId,
to: { id: conversation.id, type: conversation.type },
message,
};
}
将生成的 JSON 提交至 POST /v1/messages,在服务端使用 X-Api-Key 鉴权和 Content-Type: application/json。示例只提供 message.url:文档要求至少一个来源,但没有说明多个来源相互冲突时的优先级。
不要直接把收到的附件对象粘贴进发送请求。入站媒体字段指南中的 attachments[].type、url、mimetype 等是接收字段,不是完整出站请求。若原地址已经过期,先按下载链接恢复指南确认对应接口的恢复方式,再决定是否转发。
按失败环节排查
| 现象 | 下一步检查 | 不应采取的动作 |
|---|---|---|
提供了本地路径、相对地址或 data: URL | 准备完整 HTTP(S) 文件来源 | 把路径改名成 file_key |
| 只有自己的浏览器能打开 | 登录依赖、有效期、重定向及响应内容 | 假设发送方继承浏览器会话 |
unsupported_message_type | 渠道与媒体类型组合 | 重复提交同一请求 |
provider_not_ready | 授权状态与运行状态 | 未诊断便重新授权 |
| 请求超时 | 保留出站操作记录,调查不确定结果 | 自动重发,造成潜在重复消息 |
响应为 accepted | 保存返回标识,另查支持的送达证据 | 立即显示“已送达”或“已读” |
按错误参考中的机器可读错误分支处理,不要猜测错误描述文字。保留 request_id 供排查,但不要把它当作去重令牌;请求追踪指南进一步说明了这一边界。
处理回执时,查阅事件文档及渠道事件矩阵,验证 webhook 签名,并容忍重复和乱序。并非所有渠道都有每种回执,因此缺少确认应保留为未知,而不是触发再次发送。
验收检查与常见问题
上线前建议覆盖:可访问文件、已过期来源、返回登录页、不支持的渠道/类型组合、账号断连以及发送响应丢失。本文不声称已执行这些线上测试。
能通过这个 JSON 请求直接上传本地文件吗?
已记录的媒体请求使用 URL 或文件 key 来源。本文不提供或承诺 multipart 上传端点。请通过获准存储流程托管文件,再提供可访问 URL。
Telegram file_id 可以作为 file_key 吗?
文档没有建立这种对应关系。Telegram 原生文件标识与 UnifyPort 来源字段应分开处理。
accepted 代表对方收到文件了吗?
不代表。它表示请求已接受,而不是送达或已读回执。
下一步与参考资料
按发送图片和文件指南,先用一个获准测试会话和一个受控文件来源验证流程,再开放排队或自动发送。
参考资料核对日期:2026-10-09。
让消息接入变成一条稳定的产品管线。
先用统一 API 跑通发送,再用标准事件把所有入站消息接回业务系统。