WhatsAppの引用返信:reply_tokenと親メッセージIDの違い
UnifyPortでWhatsAppのメッセージを引用して返信するには、選択したメッセージの data.message.reply_token を変更せず、別の送信リクエストの reply_to.reply_token に渡します。data.message.reply_to_message_id は代用できません。これは受信した引用返信の親メッセージを示す値であり、今受信したメッセージそのものではありません。トークンがなければ、IDから生成したり、黙って通常メッセージとして送信したりしないでください。
要点
- 現在のメッセージID、親ID、不透明な返信トークンを分けて扱います。
- 送信アカウントと宛先は、最新のメッセージではなく選択したメッセージから取得します。
- 現在、文書化されている引用送信操作はWhatsApp専用です。LINEにも同じ機能があるとは限りません。
- 履歴メッセージには返信トークンがありません。通常送信への切り替えは明示的に判断します。
どのメッセージが引用されるのか
仮の会話を考えます。Aが質問し、BがAを引用して訂正を加えました。担当者はBを引用して返信したいとします。
A ← B ← 新しい返信
標準Webhookリファレンスでは、次の値が区別されています。
| 受信したBのフィールド | 意味 | アプリケーションでの用途 |
|---|---|---|
data.message.id | Bの識別子 | Bを保存し、受信トレイで選択する |
data.message.reply_to_message_id | Aの識別子 | Bの親への参照を表示する |
data.message.reply_token | Bを引用するための不透明なハンドル | 変更せず送信リクエストへ渡す |
data.conversation.id と type | 元の会話 | 送信先を指定する |
account_id | 接続済みのメッセージングアカウント | 正しい送信アカウントを維持する |
画面でAを選ぶなら、A自身の保存済みトークンが必要です。Bの親IDでは代用できません。親がローカルに保存されていなければ、参照先を表示できないことを示し、本文を推測しないでください。
これはHTTPのWebhookレスポンス内でメッセージを送れるかという話とは別です。Webhookレスポンスと独立リクエストの比較は送信方法を扱います。本記事の焦点は、送信する引用の対象です。
選択したイベントからリクエストを作る
受信イベントを認証し、永続化してから処理します。signing_secret を設定し、Webhook配信仕様に従って、タイムスタンプ、ドット、リクエストの生のボディを連結した値のHMAC-SHA256を検証します。タイムスタンプの鮮度確認と重複排除は別の検査です。HMACリプレイ対策の解説も参照してください。
以下はリクエストを組み立てる関数であり、完全な受信サーバーや自動送信ループではありません。入力は検証・保存済みのリアルタイムイベントで、権限のある担当者が選択したものを想定します。関数名やエラー文字列はアプリケーション側のコードで、APIフィールドやサービスのエラーコードではありません。
function buildQuotedReply(event, text) {
const conversation = event.data?.conversation;
const message = event.data?.message;
if (event.type !== 'message.received' ||
event.provider !== 'whatsapp' ||
message?.direction !== 'inbound') {
throw new Error('Select an inbound WhatsApp message');
}
if (!event.account_id || !conversation?.id || !conversation.type) {
throw new Error('Missing destination context');
}
if (typeof message.reply_token !== 'string' || !message.reply_token) {
throw new Error('Quoted reply unavailable');
}
if (typeof text !== 'string' || !text.trim()) {
throw new Error('Reply text is required');
}
return {
account_id: event.account_id,
to: { id: conversation.id, type: conversation.type },
message: { type: 'text', text },
reply_to: { reply_token: message.reply_token }
};
}
生成したボディは、引用返信APIリファレンスに従い、X-Api-Key で認証した POST /v1/messages で送ります。キーはバックエンドに保持します。グループではconversationがグループを識別します。data.sender.id に置き換えると、引用対象ではなく宛先を変更してしまいます。
送信直前に、操作者がそのアカウントと会話にアクセスできることを確認します。選択したメッセージの識別子をローカルの送信タスクに保存し、新着メッセージで対象が変わらないようにします。保存済みトークンへのアクセスを制限し、汎用ログやAIへのプロンプトに含めないでください。
トークン欠落、無効、プロバイダー非対応を分ける
| 状況 | 文書化された制約 | 推奨対応 |
|---|---|---|
| リアルタイムメッセージにトークンがない | WhatsAppの返信トークン署名が設定されている場合に提供される | イベントの取得元と設定を確認し、通常送信を明示的な選択肢にする |
conversation.history から取得したメッセージ | 履歴に reply_token は含まれない | トークンを生成せず、履歴からの復元も約束しない |
400 invalid_reply_token | 改変、異なるキーによる暗号化、その他の読み取り不能 | 保存値とシリアライズ処理を確認し、エラーを調査用に残す |
501 unsupported_by_provider | 選択したプロバイダーが引用送信に未対応 | この引用送信経路を無効にし、無制限に再試行しない |
reply_to を省略 | 通常メッセージとして送信される | 明示的に切り替えを決める |
Webhookの signing_secret は配信を認証するためのものです。変更すれば読み取り不能な暗号化返信ハンドルを修復できるとは考えないでください。エラーリファレンスはエラーの意味を定義していますが、トークンの修復手段や有効期間を保証していません。
受け入れテストと適用範囲
BがAを引用していても、Bを選択した返信がBを引用することを確認します。下書き中の新着、グループ、トークン欠落、履歴のみのメッセージ、重複配信もテスト対象です。重複受信で別の送信タスクを作らない設計にします。これは推奨テストであり、実施済みの結果ではありません。
実際の送信結果を記録してください。data.status: accepted は既読通知ではなく、ネットワークのタイムアウトも未送信の証明にはなりません。無条件に再送したり、エラー後に reply_to を自動削除したりしないでください。
UnifyPortは非公式インターフェースを提供します。イベント形式が共通でも、引用送信が全チャネルで利用できるわけではありません。Telegramの公式 Bot APIリファレンスには独自の reply_parameters と ReplyParameters があり、このリクエストには流用できません。ネイティブ機能が必要なら対応するネイティブAPIを選びます。UnifyPortにはRESTのメッセージ履歴読み取りAPIも、未受信ペイロードの再配信保証もありません。
よくある質問
reply_to_message_idをreply_to.reply_tokenに入れられますか?
いいえ。前者は受信メッセージの親を指し、後者には選択したメッセージの不透明なトークンを変更せず指定する必要があります。
履歴メッセージのIDがあれば引用できますか?
対応するトークンがなければ、この文書化された操作では引用できません。履歴ペイロードにはトークンがありません。通常メッセージは明示的な代替手段として扱ってください。
UnifyPort経由のLINE、Telegram、Zaloでも使えますか?
現在の引用送信リファレンスはWhatsApp専用です。受信時に引用関係が含まれることから、送信にも対応すると推測しないでください。
次のステップと出典
受信トレイの引用ボタンを有効にする前に、引用返信リファレンスに沿って選択対象の検査を実装してください。
確認日:2026-09-26。
メッセージ連携を安定したプロダクトパイプラインへ。
まずは 1 つの API で送信を始め、標準イベントですべての inbound メッセージを業務システムへ戻しましょう。