← 所有文章
教學

Telegram 媒體附件統一 Webhook:相片、語音、文件、聯絡人同位置

Telegram 官方 Bot API 會將媒體內容放入 Telegram 自己的 Message 欄位,例如 photodocumentvoicecontactlocation。如果你用 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[],常見欄位包括 typeurlmimetype,以及可選嘅大小或時長資料。
  • 讀內容、下載附件或交俾 AI 分流之前,先用原始 request body 驗證 X-Device-Signature
  • 如果你仍然比較 Telegram Bot API webhook、getUpdates 同統一入站 Webhook,先睇接收路徑比較

Telegram 原始形狀,同行內收件箱需要嘅形狀

Telegram Bot API 的 Message object 好適合 bot 專案:程式可以跟住 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,入面有 longitudelatitude

儲存映射表

入站內容建議儲存欄位說明
文字或 captiondata.message.text唔好假設每條媒體訊息都有文字。
圖片、音訊、影片、文件、檔案data.message.attachments[]每個附件有標準 type;媒體 URL 可能係臨時地址,按你嘅留存策略及早處理。
語音/音訊時長attachments[].duration_ms可能不存在,當 optional 欄位處理。
文件標題attachments[].title可用於展示,唔適合作唯一鍵。
聯絡人data.message.contactTelegram 聯絡人可包含電話、姓名、vCard 同 user id。
位置data.message.location經緯度應獨立存,唔好只放文字地址。
訊息 IDdata.message.id訊息層級動作用佢;Webhook 投遞去重用頂層 id

如果你未建立接收端,先跟 Webhook-first 入站整合清單:建立 Webhook、啟用簽名、儲存標準事件,再將附件交俾後續 worker。

先驗簽,再解析

啟用 signing_secret 後,UnifyPort 會發送 X-Device-TimestampX-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);
});

生產環境請將示例 queue 換成你自己嘅資料庫或工作佇列。重點係:慢下載、OCR、AI 摘要唔應該早過可信入庫。投遞、重試同排序邊界見 Webhook delivery and signature verification;如要處理 emoji reaction,可睇統一 Webhook 處理 message.reaction

UnifyPort 應放喺邊一層

UnifyPort 提供非官方接口,將已連線訊息帳號上的 Telegram 入站訊息交付成標準事件。媒體留存幾耐、是否複製臨時檔案、邊啲內部服務可以讀附件,仍然由你嘅產品同合規設計決定。

價值在於接收層形狀統一:同一個 receiver 今日處理 Telegram 相片,之後亦可以處理 WhatsApp 圖片、LINE 相片、Zalo 訊息、TikTok DM 或 X 訊息。

限制同取捨

如果你要做 Telegram bot 身份、指令、inline keyboard、BotFather 設定或其他 bot 專屬能力,官方 Bot API 更直接。統一 Webhook 更適合既有訊息帳號入站同多渠道客服隊列。亦唔好假設每個平台都有 Telegram 所有媒體類型;儲存層可以統一,能力判斷仍應放喺平台邊界。

FAQ

Telegram 相片會放喺 data.message.text 嗎?

唔會。文字同 caption 用 data.message.text;媒體檔案用 data.message.attachments[]

同一個 Webhook 可以收聯絡人同位置嗎?

可以。message.received 可以帶 data.message.contactdata.message.location 呢類結構化欄位。

要下載完媒體先回 2xx 嗎?

通常唔需要。先存已驗簽事件,將下載同處理排入 queue,再回 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

UnifyPort API

令訊息接入變成一條穩定嘅產品管線。

先用統一嘅 API 跑通發送,再用標準事件將所有入站訊息接返去業務系統。