Telegram 媒体附件统一 Webhook:照片、语音、文档、联系人和位置怎么处理
Telegram 官方 Bot API 会把媒体内容拆成 Telegram 自己的 Message 字段,例如 photo、document、voice、contact、location。如果你接入的是 UnifyPort 统一 Webhook,就不需要把这些字段逐个写进业务模型。先存 message.received 标准事件:文本读 data.message.text,媒体读 data.message.attachments[],联系人读 data.message.contact,位置读 data.message.location。
关键结论
- Telegram 媒体消息不是“带文字的普通消息”;官方 Telegram Bot API 明确把照片、文档、语音、联系人、位置等内容作为不同可选字段呈现。
- UnifyPort 会把 Telegram 入站内容归一为
message.received,同一个形状也用于 WhatsApp、LINE、TikTok、Zalo 和 X。 - 媒体附件放在
data.message.attachments[],常见字段包括type、url、mimetype,以及可选的大小或时长信息。 - 读取正文、下载附件或进入 AI 分流之前,先用原始请求体验证
X-Device-Signature。 - 如果你还在比较 Telegram 官方 Bot API webhook 和统一入站路径,先看这篇接收路径对比。
Telegram 发来的结构,和你的收件箱要存的结构
Telegram Bot API 的 Message 对象非常适合机器人开发:你直接按 Telegram 的字段分支处理。但跨境电商、东南亚运营团队、SaaS 客服队列通常要处理不止一个渠道。数据库更适合围绕“统一入站事件”建模,而不是围绕 Telegram 单个平台的对象建模。
UnifyPort 的 Standard event types and payload 文档给出的稳定事件外壳是:
{
"id": "evt_b1a7c3e5f8",
"type": "message.received",
"provider": "telegram",
"account_id": "acc_8c21d0",
"occurred_at": "2026-06-08T12:37:00Z",
"data": {
"conversation": { "id": "5005", "type": "user" },
"sender": { "id": "4004", "type": "user", "name": "Jordan Lee" },
"message": {
"id": "3003",
"direction": "inbound",
"sent_at": "2026-06-08T12:37:00Z",
"contact": {
"phone_number": "+8600000000000",
"first_name": "Demo",
"last_name": "User",
"vcard": "BEGIN:VCARD\nVERSION:3.0\nFN:Demo User\nEND:VCARD",
"user_id": 4004
}
},
"event": { "kind": "message_received" }
}
}
照片、语音、视频和文档进入 data.message.attachments[];联系人进入 data.message.contact;位置进入 data.message.location,其中包含 longitude 和 latitude。
存储映射表
| 入站内容 | 建议存储字段 | 说明 |
|---|---|---|
| 文本或 caption | data.message.text | 不要假设每条媒体消息都有文字。 |
| 图片、音频、视频、文档、文件 | data.message.attachments[] | 每个附件有标准 type;媒体 URL 可能是临时地址,应按你的留存策略尽快处理。 |
| 语音/音频时长 | attachments[].duration_ms | 可能不存在,按可选字段处理。 |
| 文档标题 | attachments[].title | 可用于展示,不要当作唯一标识。 |
| 共享联系人 | data.message.contact | Telegram 联系人可包含手机号、姓名、vCard 和 user id。 |
| 位置 | data.message.location | 把经纬度单独存储,不要只放在文本字段里。 |
| 消息标识 | data.message.id | 消息级动作和投影用它;投递去重用顶层 id。 |
这篇文章是Webhook-first 入站集成清单的实现补充:先创建接收端,先存标准事件,再把附件分发给搜索、CRM、AI 工单或文件处理任务。
先验签,再解析
启用 signing_secret 后,UnifyPort 会发送 X-Device-Timestamp 和 X-Device-Signature。签名是下面内容的十六进制 HMAC-SHA256:
<X-Device-Timestamp>.<raw request body>
Node.js 官方文档说明了 crypto.createHmac() 和 crypto.timingSafeEqual();Express 官方文档说明 express.raw() 可把请求体保留为 Buffer。接收端应先验签,再解析 JSON。
import crypto from 'node:crypto';
import express from 'express';
const app = express();
const signingSecret = process.env.WEBHOOK_SIGNING_SECRET;
app.post('/webhooks/unifyport', express.raw({ type: 'application/json' }), async (req, res) => {
const timestamp = req.get('X-Device-Timestamp') ?? '';
const signature = req.get('X-Device-Signature') ?? '';
const expected = crypto.createHmac('sha256', signingSecret).update(timestamp + '.').update(req.body).digest();
const supplied = /^[0-9a-f]{64}$/i.test(signature) ? Buffer.from(signature, 'hex') : Buffer.alloc(0);
if (supplied.length !== expected.length || !crypto.timingSafeEqual(supplied, expected)) return res.sendStatus(401);
const event = JSON.parse(req.body.toString('utf8'));
await storeEvent(event.id, event);
if (event.type !== 'message.received' || event.provider !== 'telegram') return res.sendStatus(202);
const message = event.data.message;
for (const attachment of message.attachments ?? []) {
await mediaQueue.enqueue({
eventId: event.id,
messageId: message.id,
conversationId: event.data.conversation.id,
type: attachment.type,
url: attachment.url,
mimetype: attachment.mimetype,
title: attachment.title,
durationMs: attachment.duration_ms
});
}
if (message.contact) await contactQueue.enqueue(message.contact);
if (message.location) await geoQueue.enqueue(message.location);
return res.sendStatus(202);
});
生产环境中,把示例队列替换成你的数据库或任务系统。边界原则不变:慢下载、OCR、AI 摘要都不要发生在“可信入库”之前。验签、重试和幂等细节见 Webhook delivery and signature verification;如果你还要处理表情回应状态,可继续看统一 Webhook 处理 message.reaction。
UnifyPort 适合放在哪里
UnifyPort 提供非官方接口,把已连接消息账号上的 Telegram 入站消息送成统一事件。你仍然负责决定媒体留存周期、是否复制临时文件、哪些 worker 可以访问附件。
好处是模型可以复用:同一个接收端今天处理 Telegram 图片,明天也能接收 WhatsApp 图片、LINE 照片、Zalo 消息、TikTok DM 或 X 消息,而不需要把 Telegram 的 Message 对象变成数据库中心。
限制与取舍
如果你要做 Telegram 机器人身份、bot command、inline keyboard、BotFather 配置或其他机器人特性,官方 Bot API 更合适。统一 Webhook 更适合“已有账号入站”和“多渠道队列”。另外,不要假设所有平台都有 Telegram 的每一种媒体形态;存储层统一,能力判断仍要放在边界处。
FAQ
Telegram 照片会放在 data.message.text 吗?
不会。文本和 caption 在 data.message.text;媒体文件在 data.message.attachments[],并带有标准附件 type。
同一个 webhook 能接收联系人和位置吗?
可以。message.received 可以携带 data.message.contact 或 data.message.location 这类结构化字段。
是否要下载完媒体再返回 2xx?
通常不要。先存已验签事件,把媒体下载放进队列,然后返回 2xx。
这和 Telegram Bot API webhook 一样吗?
不一样。Telegram Bot API webhook 接收的是 bot token 对应机器人的 Update;UnifyPort webhook 接收的是已连接消息账号的标准事件,并可与其他平台共用接收端。
下一步
打开 Create webhook endpoint,订阅 message.received,启用 signing_secret,并分别测试文本、附件、联系人和位置消息。
Sources checked on 2026-09-08
- Telegram Bot API: https://core.telegram.org/bots/api
- Node.js Crypto documentation: https://nodejs.org/api/crypto.html
- Express middleware documentation: https://expressjs.com/en/5x/guide/using-middleware
- UnifyPort Standard event types and payload: /zh-CN/docs/receiving-events/webhook-events/
- UnifyPort Webhook delivery and signature verification: /zh-CN/docs/receiving-events/webhook-delivery/
让消息接入变成一条稳定的产品管线。
先用统一 API 跑通发送,再用标准事件把所有入站消息接回业务系统。