How WhatsApp messages are delivered
The status ladder from queued to read, the 24-hour reply window, template vs free-form messages, and how Meta's webhooks update every message.
How WhatsApp messages are delivered
Every WhatsApp message in the CRM carries a status: it was queued, it is sending, Meta accepted it, the phone received it, the person read it — or it failed, with a reason. This page traces one message from the inbox (or an automation) to the customer's phone and back, and explains the rule that trips everyone up: the 24-hour reply window.
The status ladder in plain language
| Status | Meaning |
|---|---|
| Queued | The CRM has accepted the message and it is waiting to be sent |
| Sending | The provider call is in flight |
| Sent | Meta accepted the message for delivery |
| Delivered | The recipient's phone received it |
| Read | The recipient opened the chat (when Meta reports it) |
| Failed | Something went wrong — the reason is stored on the message |
Statuses only ever move forward. A late "delivered" webhook cannot overwrite a "read"; nothing downgrades. Failed is terminal.
stateDiagram-v2
[*] --> QUEUED
QUEUED --> SENDING
SENDING --> SENT
SENT --> DELIVERED
DELIVERED --> READ
SENDING --> FAILED
SENT --> FAILED
DELIVERED --> FAILED
FAILED --> [*]
The 24-hour reply window
WhatsApp is strict about who may start a conversation:
- Inside the window (within 24 hours of the customer's last incoming message): you can send free-form messages — anything your team types in the inbox.
- Outside the window: you can only send an approved message template (marketing/utility/authentication). Attempting free-form outside the window fails with Meta error 131047 ("re-engagement message").
The CRM tells you which mode you are in:
- The WhatsApp inbox flags conversations where the window has closed (the composer switches to templates).
- Automations that send WhatsApp messages describe the template they use; the send path prefers a template descriptor when present.
- The connection's approved templates are managed under Message templates.
"Failed: 131047" is not a bug If a free-form message failed with 131047, the customer has not written to you in 24 hours. Send an approved template instead — the message failed by WhatsApp's design, not by fault of the CRM.
The technical trace
- Queued. Something wants to send: a reply in WhatsApp inbox, an automation's
send_whatsappaction (with its guard chain), or a scheduled sweep (appointment + follow-up reminders run every 5 minutes). A message row is created with status queued. - Sending.
App\Services\Messaging\ChannelMessageSenderbuilds the payload and calls the WhatsApp Cloud API athttps://graph.facebook.com/{version}/{phone_number_id}/messagesviaApp\Services\WhatsApp\MetaCloudWhatsAppProvider. If a template descriptor exists it wins over free-form text. - Sent. Meta's API accepts the message and returns its WhatsApp message id. The message moves to sent.
- Status webhooks. Meta posts delivery callbacks for
sent,delivered,read,failedto the messaging webhook, received byMessagingWebhookController. Signature is verified (X-Hub-Signature-256). - Monotonic application.
App\Services\WhatsApp\WhatsAppMessageService::applyStatus()advances the status using itsSTATUS_RANKladder — queued(0) → sending(1) → sent(2) → delivered(3) → read(4); failed(5) is terminal. A status never regresses. - Timeline + UI. Delivered/read/failed events also write activities, so the conversation's timeline in the inbox shows exactly when the customer saw the message — and the failure reason when one exists.
- Opt-out. Incoming STOP replies mark the lead as opted out (
leads.whatsapp_opted_out_*); future sends to that lead are skipped by the guard chain.
Local install note With QUEUE_CONNECTION=sync (the XAMPP default) the send happens inline in the request — you see "sending → sent" immediately, but there is no retry queue. Production uses the database queue plus a worker; a stopped worker shows up as messages sitting in queued.
Troubleshooting notes
| Symptom | Likely cause | Fix |
|---|---|---|
| Messages stuck in Queued | Queue worker not running (production) | Start/restart the worker; see Cron & queue workers |
| Message Failed with 131047 | Free-form sent outside the 24-hour window | Use an approved template |
| Message Failed with a token/permission error | Connection token expired or revoked | Reconnect WhatsApp in Connect WhatsApp |
| Template send fails | Template not approved, not synced, or parameter mismatch | Sync Templates on the WhatsApp settings page; check the template definition |
| Status stuck on Sent for days | Recipient device offline — normal WhatsApp behaviour | Delivered arrives when the phone next connects; "sent" is still valid |
| No status updates at all | Status webhook not subscribed or signature failing | Check the WhatsApp webhook configuration and app secret |
Related guides
Was this page helpful?
Thanks — noted. ✦