Telegram のメディア添付を統一 Webhook で処理する:写真、音声、ドキュメント、連絡先、位置情報
Telegram 公式 Bot API では、メディアは photo、document、voice、contact、location など Telegram 固有の Message フィールドとして届きます。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 ルーティングに渡す前に、必ず raw body で
X-Device-Signatureを検証します。 - Telegram の
getUpdates/ Bot API webhook / 統一 Webhook の違いから確認したい場合は、先に受信パス比較を読んでください。
Telegram 固有の Message と、受信箱で保存したい形
Telegram Bot API の Message オブジェクトは Telegram bot には自然です。しかし日本のチームでは、Telegram だけでなく LINE の問い合わせも同じサポート画面に入れたい、という要件がよくあります。その場合、保存モデルは 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 に入ります。
メディア別の保存マップ
| 受信内容 | 保存するフィールド | メモ |
|---|---|---|
| テキストまたは caption | data.message.text | メディアメッセージに必ずテキストがあるとは限りません。 |
| 画像、音声、動画、ドキュメント、ファイル | data.message.attachments[] | 各添付は正規化された type を持ちます。URL は一時的な場合があるため、保存方針に従って早めに処理します。 |
| 音声の長さ | attachments[].duration_ms | 任意フィールドとして扱います。 |
| ドキュメント名 | attachments[].title | 表示用には便利ですが、一意キーにはしません。 |
| 連絡先 | data.message.contact | Telegram では電話番号、名前、vCard、user id を含むことがあります。 |
| 位置情報 | data.message.location | longitude と latitude をテキストとは別に保存します。 |
| メッセージ ID | data.message.id | メッセージ単位の処理に使います。Webhook 配信の重複排除はトップレベルの id で行います。 |
受信基盤をこれから作る場合は、まず Webhook-first inbound integration checklist の順序で、Webhook 作成、署名有効化、標準イベント保存を済ませてください。
検証してから 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 として扱う middleware と説明されています。したがって、JSON パース前に raw 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 要約、CRM 連携などの遅い処理に進むことです。配信、再試行、順序の詳細は Webhook delivery and signature verification を参照してください。リアクションも扱う場合は、message.reaction の処理チュートリアル が近い実装例です。
UnifyPort を置く場所
UnifyPort は非公式インターフェースとして、接続済み messaging account の Telegram 入站メッセージを標準イベントで届けます。メディアをどれくらい保持するか、一時 URL をコピーするか、どの worker が添付を読めるかは、引き続きあなたのアプリケーション設計です。
利点は、受信層の再利用です。同じ receiver で Telegram の写真を扱い、後から LINE の写真、WhatsApp 画像、Zalo メッセージ、TikTok DM、X メッセージを追加できます。
制限と判断基準
Telegram bot のアイデンティティ、bot command、inline keyboard、BotFather 設定などが中心なら、公式 Bot API が適しています。既存の messaging account の受信、または LINE を含む複数チャネルの受信キューを作るなら、統一 Webhook が適しています。すべてのプロバイダーが Telegram と同じメディア種別を持つとは限らないため、能力チェックはチャネル境界に残してください。
FAQ
Telegram の写真は data.message.text に入りますか?
いいえ。テキストや caption は data.message.text、メディアファイルは data.message.attachments[] に入ります。
同じ Webhook で連絡先と位置情報も受け取れますか?
はい。message.received は data.message.contact や data.message.location のような構造化フィールドを持てます。
メディアをダウンロードしてから 2xx を返すべきですか?
通常は不要です。検証済みイベントを保存し、メディア処理を queue に入れてから 2xx を返します。
Telegram Bot API webhook と同じですか?
違います。Bot API webhook は bot token に紐づく Telegram Update を受け取ります。UnifyPort webhook は接続済み messaging account の標準イベントを受け取ります。
次のステップ
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: /ja/docs/receiving-events/webhook-events/
- UnifyPort Webhook delivery and signature verification: /ja/docs/receiving-events/webhook-delivery/
メッセージ連携を安定したプロダクトパイプラインへ。
まずは 1 つの API で送信を始め、標準イベントですべての inbound メッセージを業務システムへ戻しましょう。