The WhatsApp 24-hour window: what opens it, what closes it, and where the math goes wrong
Every Cloud API integration runs into the same rule, and most of them get the same piece of it wrong. The window doesn't start when your server processes the webhook — it starts at the timestamp Meta sent inside it.

What you'll take away
- Why anchoring to the wrong clock produces 131047 on a conversation your own UI shows as open
- The inbound message that does NOT open the window
- Why flows and interactive messages are not an exception
The rule, plainly
Inside the customer service window you can send anything: text, media, interactive messages, flows. Outside it, approved templates only. There is no middle ground and no message category that slips through — Meta rejects with error 131047 and the message never leaves.
The window opens when the customer messages you and lasts 24 hours from that message. Every new customer message restarts the clock. Easy to state, and it is precisely because it sounds easy that nearly every implementation misses the same detail.
The right clock is Meta's, not yours
Meta's webhook carries a timestamp field holding the moment the message reached them, in Unix seconds. That instant is what opens the window. The moment your server processed the webhook is not.
They look identical and almost always are — until the day they aren't. Webhooks get redelivered. If Meta replays an event late, or your queue backs up during an incident, your math starts granting a window more generous than the real one. Your system is convinced it can send free-form text, Meta disagrees, and 131047 shows up on a conversation your own screen renders as open.
The symptom is nasty because your UI and Meta tell different stories about the same fact, and the UI looks trustworthy. We moved the window onto the payload timestamp after a welcome auto-reply failed exactly this way, with the conversation showing open in the interface.
The inbound that opens nothing
Not every inbound opens the window. When a customer sends something the Cloud API can't represent — certain attachment types, system messages, anything Meta classifies as unsupported — the event reaches your webhook typed unsupported, usually carrying error 131051 in the errors array.
That event opens no window on Meta's side. If your type router drops unsupported into the same bucket as interactive (an easy mistake, since they look structurally alike), you just recorded a window that doesn't exist — and you're about to fire an auto-reply and an AI agent at a conversation that only accepts templates.
The rule that stuck in our code: a message typed unsupported, or carrying anything in errors[], opens no window, triggers no auto-reply and doesn't wake the AI. It gets recorded in the thread so the agent can see something arrived, and that's it.
Flows and interactive messages are not exempt
There's a common belief that interactive messages and flows escape the window because they're “structured”. They don't. A flow sent outside the window is rejected the same as loose text.
To re-engage someone outside the window with a flow, the flow has to be wrapped in an approved flow template — a different object with its own approval cycle. It is not a flag on the send call.
Worth checking in your own guard before calling Meta. Blocking locally with a clear error costs milliseconds; finding out via 131047 costs an API round trip, an error entry in the customer's history and, depending on your retry policy, a ladder of attempts that will never succeed.
The three rules that stayed in the code
timestamp (Unix seconds) and use it as the window start. Never your processing time.unsupported as a non-eventType unsupported or a non-empty errors[]: record it in the thread, open no window, trigger no automatic reply.