【ITニュース解説】WhatsApp Message Statuses Arrive Out of Order: Modelling Sent, Delivered, Read and Failed
2026年10月29日に「Dev.to」が公開したITニュース「WhatsApp Message Statuses Arrive Out of Order: Modelling Sent, Delivered, Read and Failed」について初心者にもわかりやすく解説しています。
ITニュース概要
WhatsAppのメッセージステータス処理は、Webhookの順不同・重複・省略で複雑化する。正確な管理には、ステータスを「進行方向のみ」に更新し、各タイムスタンプを個別に保持するのが重要。未知IDのステータスは一時保存し、DBでアトミックに処理する設計が必要だ。
ITニュース解説
WhatsAppのようなメッセージングサービスでメッセージを送信すると、そのメッセージがどのような状況にあるかを示す「送信済み」「配信済み」「既読」「失敗」といったステータス情報がシステムに送られてくる。これらのステータスは、メッセージが相手に届いたか、読まれたかといった重要な情報を表すため、多くのシステムではこれらの情報に基づいて画面を更新したり、後続の処理を進めたりする。しかし、システムエンジニアを目指す初心者がこれらのステータスを扱う際、想像以上に複雑な課題に直面することがある。単純に「最新のステータスが届いたら、その情報を保存する」という考え方で処理を実装すると、データの整合性が崩れてしまう可能性があるためだ。
具体的には、「既読」になったメッセージが、後から届いた「配信済み」のステータスによって再び「配信済み」の状態に戻ってしまったり、メッセージの送信が「失敗」したはずなのに、遅れて届いた「送信済み」のステータスによって「送信済み」に上書きされてしまったりすることが起こり得る。また、メッセージを送信した直後、まだそのメッセージの情報をシステムに保存する前(データベースにメッセージIDを登録する前)にステータスが届いてしまい、その貴重な情報が失われるといった事態も発生する。これらの問題は、ぱっと見では気づきにくく、システムの信頼性を大きく損なう原因となる。
このような課題が発生する背景には、WhatsAppの提供元であるMeta社のWeb APIが送ってくるステータス情報のいくつかの特性がある。まず、Meta社のドキュメントでは、ステータスが「特定の順序で届くことは保証されていない」と明記されている。つまり、「送信済み」の後に「既読」が届き、その後に「配信済み」が届くといった、本来の時間の流れとは逆の順序でステータスが届く可能性がある。それぞれのステータスは独立した情報として個別のリクエストで送られてくるため、開発者はどのような順序でステータスが届いても正しく処理できるようにシステムを設計する必要がある。次に、「ステータスが重複して届く可能性がある」という点も重要だ。システムがステータス情報を受け取った際に「正しく受け取った」という応答をMeta社に返さないと、Meta社は最大7日間、頻度を減らしながらそのステータス情報を再送しようとする。この再送の仕組みにより、同じステータス情報が複数回届くことがあり、システムはその重複を適切に処理しなければならない。さらに、「すべてのステータスが必ず届くとは限らない」という特性もある。例えば、メッセージがユーザーのデバイスに配信されると同時にユーザーがチャット画面を開いてメッセージを読んだ場合、Meta社は「配信済み」のステータスを送らずに、いきなり「既読」のステータスだけを送ってくることがある。「既読」になったということは、「配信済み」でもあることが暗黙的に示されるためだ。もしシステムが「配信済み」のステータスが届くのを待ってから「既読」を表示するように作られていると、そのメッセージは永遠に「既読」にならないままになってしまう。
これらのMeta APIの特性に対処し、メッセージのステータスを正確かつ堅牢に管理するためには、いくつかの重要な設計原則を適用する必要がある。
一つ目の原則は、「メッセージのステータスは常に順方向にのみ進む」という考え方である。メッセージのステータスには「受け付け済み」「送信済み」「配信済み」「既読」という段階があり、これらには明確な進行順序がある。システムは、今よりも上位のランクを持つステータスが届いた場合にのみ、そのメッセージの主要なステータスを更新するようにする。例えば、「既読」の状態にあるメッセージに、後から「配信済み」のステータスが届いても、それは「既読」よりも低いランクの状態なので、メッセージの主要なステータスを「配信済み」に戻すようなことはしない。ただし、「失敗」ステータスはこれらの順序とは異なり、メッセージがまだ「配信済み」や「既読」になっていない場合にのみ適用される特殊な状態として扱う。
二つ目の原則は、「各ステータスの発生時刻をそれぞれ独立して保持する」ことである。メッセージの現在の最終ステータスだけでなく、「いつ送信されたか」「いつ配信されたか」「いつ読まれたか」「いつ失敗したか」といった具体的な日時情報を、それぞれ sentAt、deliveredAt、readAt、failedAt のように別々のデータ項目として保存する。これにより、たとえ遅れて「配信済み」のステータスが届いたとしても、そのイベントが発生した時刻を正確に記録でき、後からメッセージの応答時間や既読率などのレポートを作成する際に正確な情報を提供できる。
三つ目の原則は、「既読ステータスから配信済みステータスを推論する」ことである。Meta社が「既読」のステータスだけを送ってくる場合があるため、「既読」のステータスが届いたら、そのメッセージは「配信済み」にもなっていると見なし、配信時刻もその時点で設定する。もし後から実際の「配信済み」ステータスが届き、その時刻が先に設定した時刻よりも早ければ、より早い方を採用して正確な配信時刻を記録する。
四つ目の原則は、「失敗ステータスの扱いに細心の注意を払う」ことである。「失敗」のステータスは、メッセージがユーザーのデバイスに「配信済み」または「既読」になる前に届いた場合にのみ有効と見なす。一度メッセージが「配信済み」または「既読」になった後で「失敗」ステータスが届いても、それは矛盾した情報なので無視する。また、一度「失敗」と記録されたメッセージは、後から「送信済み」などのステータスが届いても、成功状態に戻すことは決してしない。
これらのルールをシステムで正しく適用するためには、技術的な工夫も必要となる。特に、複数のWebhook処理が同時に同じメッセージのステータスを更新しようとする「競合状態(レースコンディション)」を防ぐ必要がある。この問題に対処するためには、データベースにステータスを更新する際に、単一のデータベース操作(例えばSQLの UPDATE 文)の中で、これらのルール(順方向にのみ進む、タイムスタンプの優先順位など)をすべて適用させるようにする。これにより、データベースがその更新処理をアトミックに(分割不可能な一つの処理として)実行するため、複数の処理が同時に行われてもデータの整合性が保たれる。具体的には、データベースのテーブルに「status_rank」のようなカラムを追加し、各ステータスのランクを数値で保存することで、更新ロジックを簡潔に表現できる。
さらに、メッセージを送信した直後、Meta社からメッセージIDを受け取ってシステムに保存するまでのごく短い時間にも、そのメッセージのステータス情報(特に「送信済み」)が届いてしまうという問題にも対処が必要である。メッセージIDがまだデータベースに存在しないため、通常の検索では情報が見つからず、せっかく届いたステータス情報が失われてしまう。この問題の解決策は、「メッセージIDが不明な状態で届いたステータスは一時的に保留しておく」ことである。そして、メッセージIDがデータベースに保存され、そのメッセージの行が作られた際に、保留しておいたすべてのステータス情報を改めて適用(再生)し直すのである。これにより、システムはメッセージの最初の状態から正確なステータス履歴を構築できる。
これらの設計原則と技術的な工夫を組み合わせることで、WhatsAppのメッセージステータスのように、順不同で届き、重複する可能性があり、一部が省略されるかもしれない非同期な情報ストリームであっても、信頼性の高いシステムを構築することが可能となる。システムエンジニアを目指す上で、このようなWeb APIの現実的な課題とその堅牢な解決策を理解することは、非常に重要な学びである。常に「最新の情報で上書きすれば良い」という安易な考え方ではなく、情報の特性を深く理解し、それに応じた設計を心がけることが、安定したシステムを開発する上で不可欠なのである。