← 全記事
チュートリアル

WhatsApp共有受信箱で既読・未読状態を同期する方法

WhatsAppの共有受信箱では、Webhookを受信しただけで会話を既読にするべきではありません。担当者が実際に引き受けた時点、または対応を完了した時点で更新します。UnifyPortでは、conversation_idを指定して会話を既読にできます。特定メッセージまで開封扱いにする場合は、メッセージIDと送信者IDを必ずセットで渡します。再対応が必要なら未読に戻します。

要点

  • 会話の既読・未読アクションは、現時点ではWhatsAppのみ対応しています。非対応のプロバイダーとアクションの組み合わせは501 unsupported_by_providerになります。
  • POST /v1/accounts/{account_id}/conversations/readにはconversation_idが必要です。任意で特定メッセージまで既読にできます。
  • up_to_message_idup_to_message_sender_idはペアです。片方だけを送ると400 invalid_requestになります。
  • 未読に戻す操作に必要なのはconversation_idだけです。
  • 担当、保留、解決といった業務状態は自社システムに保存します。WhatsApp側の既読状態は、サポートキュー全体そのものではありません。

3種類の「既読」を分ける

安定した共有受信箱では、次の3つを別の状態として扱います。

  1. 自社キューの状態:新規、担当済み、保留、解決など、アプリケーションが定義する状態です。
  2. 接続したWhatsAppアカウントのチャット一覧状態:既読または未読です。会話を既読にするAPI未読にするAPIで変更します。
  3. 受信者の開封イベントmessage.read Webhookは、アカウントから送ったメッセージを相手が読んだことを示します。担当者が受信チケットを開いたという意味ではありません。

ローカルな会話設定が変わると、UnifyPortはconversation.updatedイベントをマッピングできる場合があります。data.conversation.idがチャットを識別し、既読状態の変更はdata.readに現れます。このイベントは照合用のシグナルとして扱い、誰がいつ担当し、なぜ再オープンしたかは自社データベースに残してください。

イベントを適用する前に、raw bodyの署名を検証し、再送を冪等に処理します。受信処理の境界はWebhook HMAC・リプレイ対策・再送ガイドで確認できます。受信箱関連だけが必要なら、Webhookイベントフィルターのチュートリアルを使い、必要なイベントだけを明示的に購読します。

プロバイダー側の状態を変えるタイミング

すべての受信Webhookを自動で既読にすると、未対応のキューまで空に見えてしまいます。次のような明示的なルールを設定します。

チームの操作自社キューWhatsAppの操作
受信メッセージを保存newなし
担当者が引き受けるassigned必要に応じて対象メッセージまで既読
対応を完了するresolved会話全体を既読
後で対応するwaiting会話を未読
割り当て前に自動処理が失敗newなし

この分離により、ブラウザーの再読み込み、Webhookの再送、バックグラウンドのプレビューで未対応案件が消えることも防げます。

WhatsApp会話を既読にする

プロバイダーIDには@:が含まれることがあるため、conversation_idはURLではなくJSON bodyに入れます。

会話全体を既読にするリクエストです。

curl -X POST "https://api.unifyport.ai/v1/accounts/$UNIFYPORT_ACCOUNT_ID/conversations/read" \
  -H "X-Api-Key: $UNIFYPORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "8613912345678@s.whatsapp.net"
  }'

特定の受信メッセージまでWhatsAppの開封扱いにする場合は、同じmessage.receivedイベントから3つのIDを取得します。

{
  "conversation_id": "120363041234567890@g.us",
  "up_to_message_id": "CURRENT-MESSAGE-ID",
  "up_to_message_sender_id": "8613912345678@lid"
}

グループでは、up_to_message_sender_idに一致するdata.sender.idを指定します。会話IDから推測してはいけません。メッセージ単位の処理が不要なら、2つのup_to_message_*フィールドを両方とも省略します。

次のNode.js helperは、フィールドのペアを確認してからAPIを呼び出します。

const apiBase = 'https://api.unifyport.ai/v1';

async function setWhatsAppReadState({ accountId, conversationId, unread, message }) {
  const action = unread ? 'unread' : 'read';
  const body = { conversation_id: conversationId };

  if (!unread && message) {
    if (!message.id || !message.senderId) {
      throw new Error('message.id and message.senderId must be supplied together');
    }
    body.up_to_message_id = message.id;
    body.up_to_message_sender_id = message.senderId;
  }

  const response = await fetch(
    `${apiBase}/accounts/${encodeURIComponent(accountId)}/conversations/${action}`,
    {
      method: 'POST',
      headers: {
        'X-Api-Key': process.env.UNIFYPORT_API_KEY,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(body)
    }
  );

  if (!response.ok) {
    const failure = await response.json();
    throw new Error(`${response.status} ${failure.error?.code ?? 'unknown_error'}`);
  }

  return response.json();
}

署名検証済みのイベントハンドラーでは、ドキュメントのフィールドをそのまま使います。

await setWhatsAppReadState({
  accountId: event.account_id,
  conversationId: event.data.conversation.id,
  unread: false,
  message: {
    id: event.data.message.id,
    senderId: event.data.sender.id
  }
});

フォローアップする会話を未読に戻す

未読アクションはより単純です。

await setWhatsAppReadState({
  accountId: event.account_id,
  conversationId: event.data.conversation.id,
  unread: true
});

チームが明示的に案件を再オープンしたときに使います。プロバイダーの未読状態だけをリマインダーにせず、担当者、期限状態、理由を自社キューに保存してください。

同期ループを作らずに照合する

アプリケーションが会話アクションを実行すると、対応するconversation.updatedがWebhookに届くことがあります。自社から開始した操作を内部レコードに残し、受信イベントは確認に使います。同じアクションを再び呼び出すトリガーにはしません。

安全な流れは次のとおりです。

  1. message.receivedを冪等に保存する。
  2. トランザクション内で自社チケット状態を更新する。
  3. プロバイダー状態のアクションを呼び出す。
  4. APIが{ "data": { "ok": true } }を返してから成功を記録する。
  5. conversation.updatedを確認、または接続アカウント側からの外部変更として処理する。
  6. 不明な場合は会話取得APIで対象会話を取得し、unread_countを比較する。

別のチャネルで同じUIを有効にする前に、プロバイダー別アクション対応表を確認してください。統一されたルートがあっても、全プロバイダーが同じアクションに対応するとは限りません。

制限と選択基準

認証済みビジネス機能、公式テンプレート送信、プロバイダー固有のガバナンスが必要なら、公式の接続方法が適しています。UnifyPortの非公式インターフェースは通常アカウントのメッセージ運用に使えますが、各チャネルの機能を完全に同一にはしません。

この既読・未読アクションは現時点ではWhatsAppのみです。日本の共有受信箱でLINEも扱う場合、LINE側の対応状況は自社キューで管理し、このWhatsApp専用コントロールは表示しないでください。501 unsupported_by_providerを一時障害として繰り返し再試行するべきではありません。

よくある質問

message.receivedを受信すると自動的に既読になりますか?

いいえ。イベントの受信と保存だけでキューを消してはいけません。チームが定義した業務タイミングで既読APIを呼び出します。

message.readと会話を既読にする操作は何が違いますか?

message.readは相手が送信済みメッセージを読んだことを示すイベントです。会話既読アクションは、接続アカウントのローカルなチャット一覧状態を変えます。

up_to_message_idだけ送れますか?

送れません。up_to_message_sender_idと一緒に送るか、両方を省略して会話全体を既読にします。

Telegram、LINE、TikTok、Zalo、Xでも同じ操作を使えますか?

現時点では使えません。対応表では、会話の既読・未読アクションはWhatsAppのみです。非対応の組み合わせは501 unsupported_by_providerになります。

次のステップ

まず会話を既読にするAPI Referenceを確認し、自社の再オープンルールを決めてから未読アクションを追加してください。

一次情報

2026年8月21日に確認: