Telegram 媒體附件統一 Webhook:照片、語音、文件、聯絡人與位置
Telegram 官方 Bot API 會把媒體內容放在 Telegram 自己的 Message 欄位裡,例如 photo、document、voice、contact、location。如果你的接收層是 UnifyPort 統一 Webhook,就不必把這些 Telegram 專屬欄位直接變成資料庫核心模型。先存 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 分流之前,先以原始 request body 驗證
X-Device-Signature。 - 若你還在選 Telegram Bot API webhook、
getUpdates或統一入站 Webhook,先看接收路徑比較。
Telegram 的原始形狀,和收件匣需要的形狀
Telegram Bot API 的 Message 物件很適合機器人應用程式:你的程式依照 Telegram 欄位分支處理即可。但客服、CRM 或跨通路收件匣通常需要一個可以重複使用的資料合約,而不是只服務 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 表示座標。
Telegram 媒體的儲存映射
| 入站內容 | 建議儲存欄位 | 說明 |
|---|---|---|
| 文字或 caption | data.message.text | 不要要求每則媒體訊息一定有文字。 |
| 圖片、音訊、影片、文件、檔案 | data.message.attachments[] | 每個附件都有標準 type;媒體 URL 可能是暫時的,應依你的留存政策及早處理。 |
| 語音/音訊時長 | attachments[].duration_ms | 欄位可能不存在,請當作 optional。 |
| 文件標題 | attachments[].title | 可供 UI 顯示,不適合當唯一鍵。 |
| 聯絡人 | data.message.contact | Telegram 聯絡人可包含電話、姓名、vCard 與 user id。 |
| 位置 | data.message.location | 經緯度應獨立儲存,不要只靠文字地址。 |
| 訊息 ID | data.message.id | 訊息層級動作用它;Webhook 投遞去重用頂層 id。 |
如果你還沒建立接收端,先照著 Webhook-first 入站整合清單做:建立 Webhook、啟用簽名、存標準事件,再把附件交給後續 worker。
先驗簽,再解析 JSON
啟用 signing_secret 後,UnifyPort 會送出 X-Device-Timestamp 與 X-Device-Signature。簽名內容是:
<X-Device-Timestamp>.<raw request body>
Node.js 官方文件說明 crypto.createHmac() 與 crypto.timingSafeEqual();Express 官方文件說明 express.raw() 會把 payload 解析成 Buffer。因此接收端應保留原始 bytes 到驗簽完成。
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);
});
實務上,請把 mediaQueue、contactQueue、geoQueue 換成你的資料庫或工作佇列。重點是先把已驗簽事件持久化,再做慢速下載、索引、OCR 或 AI 摘要。更完整的投遞、重試與排序邊界,請看 Webhook delivery and signature verification;若還要處理表情反應,可搭配統一 Webhook 的 message.reaction 教學。
UnifyPort 放在架構哪一層
UnifyPort 提供非官方接口,將已連線訊息帳號上的 Telegram 入站訊息交付為標準事件。它不替你決定媒體保留多久,也不替你決定哪些內部服務能讀附件;這些仍是你的產品與合規設計。
它解決的是接收層的形狀問題:同一個 receiver 今天處理 Telegram 圖片,之後也能處理 WhatsApp 圖片、LINE 照片、Zalo 訊息、TikTok DM 或 X 訊息。
限制與取捨
如果你要做的是 Telegram bot 身分、指令、inline keyboard、BotFather 設定或其他機器人能力,官方 Bot API 是更直接的選擇。統一 Webhook 適合既有訊息帳號入站與多通路客服佇列。也不要假設每個平台都有 Telegram 的所有媒體型別;儲存層可統一,能力判斷仍應放在平台邊界。
FAQ
Telegram 照片會存在 data.message.text 嗎?
不會。文字和 caption 使用 data.message.text;媒體檔案使用 data.message.attachments[]。
同一個 Webhook 能收聯絡人和位置嗎?
可以。message.received 可以帶 data.message.contact 或 data.message.location 這類結構化欄位。
要下載完媒體再回 2xx 嗎?
通常不需要。先存已驗簽事件,把下載與處理排進佇列,再回 2xx。
這和 Telegram Bot API webhook 一樣嗎?
不一樣。Bot API webhook 收的是 bot token 對應的 Telegram 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-TW/docs/receiving-events/webhook-events/
- UnifyPort Webhook delivery and signature verification: /zh-TW/docs/receiving-events/webhook-delivery/
讓訊息接入變成一條穩定的產品管線。
先用統一 API 跑通傳送,再用標準事件把所有入站訊息接回業務系統。