Almost every WhatsApp integration hits the same wall in its first week, and almost every team diagnoses it wrong.
The symptom: messages sent during development go through perfectly. Messages sent from production, days later, are rejected. Nothing in the code changed.
What is actually happening#
WhatsApp distinguishes between a conversation a customer started and one a business started. When a customer messages you, a window opens during which you may reply freely — any content, no approval. That window lasts 24 hours from their most recent message.
Outside it, free-form messages are not delayed or queued. They are refused. The only thing that goes through is a template approved in advance.
In development you were inside the window, because you had just messaged yourself from a test handset. In production you were not.
Interactive figure
The 24-hour window
Window open. This will send. A free-form reply is allowed while the window is open — no template, no approval. Rates and what counts as billable have changed more than once — Meta moved to per-message billing on 1 July 2025. Check the pricing page rather than a blog post. WhatsApp pricing.
The rule is not complicated. What makes it break integrations is that it is a state machine and most send code treats it as a configuration flag.
One conversation, from the platform's point of view
- 01Closed
No customer message on file, or the last one has aged out. Only a template may be sent.
- 02The customer writes
The window opens. Free-form replies are permitted from this moment.
- 03Open
Twenty-four hours from that message. Reply in your own words, no template review, different billing.
- 04The customer writes again
The window restarts. It extends from the newest inbound message, not the first.
- 05It ages out
Back to closed. A reply written at hour twenty-five is rejected, not delayed.
Why the rule exists#
It is worth understanding rather than resenting. The window exists because the alternative — letting any business with your number start a conversation whenever it likes — is the thing that ruined SMS as a channel.
The template approval step is the price of a channel where users still read the messages. Treat it as a feature of the medium rather than an obstacle in it, and the design decisions get easier.
What to build instead#
- Model the window explicitly. Store the timestamp of the last inbound message per contact. Your send path should know, before it calls anything, which kind of message is permitted.
- Have templates ready before you need them. Approval is not instant. A support process that discovers at 2am that it needs a template it has not filed is a support process that does not reply until morning.
- Do not use a template where a reply would do. Templates are billed by category; replies inside the window are the cheap path and the more natural one.
The general lesson#
Every messaging channel has a rule of this shape — an asymmetry between conversations the customer started and conversations you started. WhatsApp's is the most visible because it is enforced by an API that says no. Email's version is enforced by a spam classifier that says nothing at all, which is worse.
What the window is actually measuring#
It is worth being precise, because teams routinely model this wrong.
The clock starts at the customer's most recent inbound message, not at the start of the conversation. Every new message from them resets it. It is per contact, not per agent, per ticket or per session — so a customer who writes to sales and is transferred to support has one window, not two, and both teams are spending it.
That last point has an operational consequence people discover the hard way: a conversation sitting in a queue is burning the window. If your support target is "reply within four hours" and the window is twenty-four, you have more headroom than you think — until a public holiday, at which point a queue of open conversations silently converts into a queue of template sends.
| Inside the window | Outside it | |
|---|---|---|
| What you may send | Free-form, in your own words | A pre-approved template only |
| Review | None. You write and it goes | Template approval, in advance, per language |
| Billing | Utility templates in an open service window became free for all businesses on 1 July 2025 | Charged per message under the current rate card |
| Who may start | The customer already did | You, but only with a template that fits an approved category |
| Failure mode | None to speak of | A rejected send at the moment you needed it, which is a support ticket |
Designing around it rather than against it#
Three patterns that work:
Surface the clock to the agent. An inbox that shows how long is left on each conversation lets a human make the right call about what to pick up next. One that does not turns an avoidable cost into a surprise.
Make the last message an invitation. If a conversation is ending but a follow-up is likely, closing with a question keeps the door open, because their reply restarts the clock. This is not a trick — it is the medium working the way it was designed to.
Batch the expiring. If several conversations are about to close and each needs a follow-up, that is a scheduled job, not a discovery at the moment of sending.
The cost model this implies#
Because free-form replies inside the window are the cheap path, the economics reward being responsive rather than being efficient. A team that answers in ten minutes spends less per resolved conversation than one that answers in ten hours, even before you count the difference in customer satisfaction.
That is an unusual alignment and worth exploiting deliberately. Most channels punish responsiveness with cost. This one rewards it.
What to take away#
- The window starts at their last message and resets on every one.
- It is per contact. Queue time spends it.
- Outside it, free-form is refused rather than queued — have templates approved in advance.
- Responsiveness is cheaper here, not more expensive. Design the operation accordingly.
Why first integrations break on it#
Three specific mistakes, and all three come from the same root: treating a conversation state as a property of the contact.
Storing "can message" on the contact record. It is true for a conversation and for a period, not for a person. A flag written at import time is wrong within a day.
Checking the window at queue time rather than send time. A message queued at hour twenty-three and sent at hour twenty-five is rejected. The check has to happen at the last possible moment.
Assuming a template can substitute for a reply. It can, and it changes the economics and the tone. A support answer delivered as an approved marketing-shaped template reads exactly as badly as it sounds.
The design that survives it#
Model the conversation. It has an id, a last-inbound timestamp, and a derived state. The send path reads that state and picks free-form or template accordingly — and it logs which one it used, because the billing question arrives later and the answer has to be retrievable.
That is perhaps thirty lines of code, and it is the difference between a channel that works and one that fails unpredictably at the twenty-fifth hour.
Sources
Every claim worth checking, with somewhere to check it.
- WhatsApp Cloud API documentationMeta for Developers
- Updates to WhatsApp Business Platform pricingMeta for Developers