OmnioChat
← All posts

What read receipts actually mean across WhatsApp, Messenger, and Instagram

By Super Admin

When building or using a multi-channel inbox, the assumption is simple: a message is sent, it is delivered to the customer's device, and eventually the customer reads it.

Three little status icons: Sent, Delivered, Read.

Under the hood, however, Meta's three messaging platforms — WhatsApp Business, Facebook Messenger, and Instagram Direct — handle delivery receipts and read acknowledgments in fundamentally incompatible ways.

If you treat all three as identical, your agents will see messages marked as read when the customer never opened them, or assume a customer is ignoring them when their phone was simply in airplane mode.

Here is what the webhooks actually emit, where they diverge, and how to handle them without breaking conversation state.

1. WhatsApp Cloud API: Explicit, Per-Message Statuses

WhatsApp has the cleanest, most granular delivery model of the three. Every single message you send generates its own discrete status progression via webhooks.

When you send an outbound message via POST /v26.0/{phone-number-id}/messages, Meta returns a message ID (wamid.HBg...). Within seconds, WhatsApp delivers webhook notifications to your callback URL containing that exact message ID:

{
  "object": "whatsapp_business_account",
  "entry": [{
    "changes": [{
      "value": {
        "statuses": [{
          "id": "wamid.HBgM...",
          "status": "sent",
          "timestamp": "1729524000",
          "recipient_id": "15551234567"
        }]
      }
    }]
  }]
}

As the message travels to the user's handset, you will receive three separate webhook callbacks:

  1. sent: Meta's servers have validated and accepted the message. (Single grey tick)
  2. delivered: The message has landed on the customer's phone or desktop client. (Double grey ticks)
  3. read: The customer opened the chat screen containing the message. (Double blue ticks)

The WhatsApp Caveats

User Privacy Settings: If the recipient has disabled "Read Receipts" in their personal WhatsApp settings, you will receive sent and delivered webhooks, but you will never receive a read webhook. Your system must treat a stuck delivered state as normal, not as a stalled delivery.

Out-of-Order Webhooks: Because webhooks are dispatched asynchronously across distributed queues, the delivered webhook can arrive before the sent confirmation, or the read webhook can arrive before the delivered event. If your database blindly overwrites status = webhook.status, you will frequently regress a message from read back to delivered.

The Fix: Enforce a strict state hierarchy in your code: pending (0) < sent (1) < delivered (2) < read (3). Only update the database record if the incoming status rank is strictly greater than the current rank.

2. Facebook Messenger: Cumulative Watermarks

Messenger does not emit a per-message "read" event. Instead, Messenger uses watermarks.

When a customer opens a thread on Facebook Messenger, Meta does not tell you which specific message ID was read. It tells you the timestamp up to which all prior messages have been read:

{
  "sender": { "id": "USER_PSID" },
  "recipient": { "id": "PAGE_ID" },
  "timestamp": 1729524050000,
  "read": {
    "watermark": 1729524045000
  }
}

The watermark value is an epoch timestamp in milliseconds.

To update your database:

UPDATE messages 
SET status = 'read', read_at = NOW()
WHERE conversation_id = ?
  AND direction = 'outbound'
  AND created_at_ms <= 1729524045000
  AND status != 'read';

Similarly, delivery receipts arrive via the delivery event:

{
  "delivery": {
    "mids": ["mid.$cAA..."],
    "watermark": 1729524040000
  }
}

Notice that while mids may be present in a delivery event, the watermark is the single source of truth. Any message sent before or at that millisecond is officially delivered.

3. Instagram Direct: The Silent Channel

Instagram Direct uses the Messenger Platform architecture, but adds several layers of user privacy and interface divergence:

The "Message Request" Black Hole

When a business messages an Instagram user who does not follow them (or vice versa depending on account privacy), the conversation lands in the user's Message Requests folder.

Until the user taps Accept:

  • No delivery webhook is sent to your webhook endpoint.
  • No read receipt is ever emitted.
  • If the user peeks at the message inside their "Requests" tab without clicking Accept, Instagram will not trigger a read webhook.

Story Mentions & Reactions

When a user replies to an Instagram Story or sends a quick emoji heart reaction (react), it creates an inbound message object, but read receipts on replies to expiring media often do not emit corresponding read watermarks.

Turning Off Read Receipts

Instagram recently rolled out a global toggle allowing users to disable read receipts for all direct messages or per-chat. Just like WhatsApp, once this is enabled by a user, you will never receive a read watermark from their profile.

Marking Inbound Messages as "Read" (The Other Direction)

Read receipts are a two-way street: when a customer messages your business, they can see whether your team has seen their message.

On WhatsApp Cloud API, when an agent opens an inbound message in your dashboard, your application should explicitly tell Meta to mark it read:

POST /v26.0/{phone-number-id}/messages
{
  "messaging_product": "whatsapp",
  "status": "read",
  "message_id": "wamid.HBgM..."
}

Calling this endpoint turns the ticks blue on the customer's phone.

Why you shouldn't do this automatically

Many poorly engineered inboxes mark every inbound webhook as "read" the millisecond it reaches the server.

This is a mistake. To the customer, blue ticks mean a real human is looking at the screen. If your system marks messages read instantly at 2:00 AM on a Sunday, the customer expects an immediate reply. When nobody responds for 8 hours, customer satisfaction plunges.

Only dispatch the "mark as read" API call when an agent actually loads that conversation in their active browser viewport.


Summary Comparison

Feature WhatsApp Cloud API Facebook Messenger Instagram Direct
Read receipt format Per-message ID (wamid) Cumulative millisecond watermark Cumulative millisecond watermark
Delivery receipt format Discrete delivered webhook watermark + optional mids array watermark + optional mids array
User can disable receipts? Yes (in WhatsApp Settings) No Yes (in IG Settings)
Works in Message Requests? N/A (phone-number based) Partial No (blocked until accepted)
Marking read from API status: "read" via /messages sender_action: "mark_seen" sender_action: "mark_seen"

Understanding these differences keeps your engineering robust and prevents your agents from making false assumptions about what their customers have seen.