The 24-hour window
When you can send free-form WhatsApp text, when you must use a template, and the 409 you get when you are wrong.
Everything that goes wrong on WhatsApp goes wrong here. If you read one page about this channel, read this one.
WhatsApp does not let a business message whoever it wants whenever it wants. You may write freely to a customer only in the 24 hours after they last wrote to you. Outside that window you may send only messages Meta has already reviewed and approved — templates. There is no third option.
Amomic-AI enforces this centrally, on the send path, so no caller can route around it: not the API, not an agent, not the console.
The window in one picture #
What opens it #
A message from the customer. Only that.
Every inbound message from that customer to that sender restarts the 24-hour clock from the moment it arrives. A customer who writes to you every day keeps the window permanently open. A customer who wrote once last Tuesday is long outside it.
Things that do not open or extend the window:
- Your agent replying. Outbound never extends the clock.
- Sending an approved template. It reaches them, but it does not open a free-form window on its own.
- A delivery or read receipt.
- The customer opening the chat, or the message being marked read.
A customer's message does not open the window when Amomic-AI doesn't take it in:
- the workspace has used its plan's conversation allowance and this would be a new conversation (the message is saved, but no window opens);
- the plan no longer includes WhatsApp and the number is in its seven-day hold;
- the number is Off with Don't keep these conversations on (the message is dropped on arrival).
In those cases a free-form reply from your team still returns 409.
The window is scoped per customer, per sender. If a customer messages one of your numbers, you have an open window on that number and no window on the others.
What you can send, when #
| Window | Free-form text |
Approved template |
|---|---|---|
| Open — they messaged within 24 hours | Yes | Yes |
| Closed — they have not, or never did | No — 409 |
Yes |
Templates deliberately bypass the window check. That is the entire reason they exist: they are the sanctioned way to start a conversation with someone who is not currently talking to you.
What templates do not bypass
Opt-outs. A suppressed recipient rejects templates exactly as hard as free text — 403 permission_denied with details.reason of "suppressed". See opt-outs.
The error, exactly #
Send free-form text to a closed window and you get this, every time:
conflictsession_window_closedThe 24-hour session window is closed; only approved template messages can be sent.{"error": {"code": "conflict","message": "The 24-hour session window is closed; only approved template messages can be sent.","details": { "reason": "session_window_closed" },"request_id": "req_01JQRS4T8XZ0AB1CD2EF3GH4"}}
Branch on error.details.reason, not on the message string. conflict covers more than one situation across the API; session_window_closed is unambiguous.
const response = await fetch("https://services.amomic.in/api/v1/messages", {method: "POST",headers: {Authorization: `Bearer ${process.env.AMOMIC_API_KEY}`,"Content-Type": "application/json","Idempotency-Key": `order-${order.id}-shipped`,},body: JSON.stringify({ to: order.customerPhone, text: message }),});if (response.status === 409) {const { error } = await response.json();if (error.details?.reason === "session_window_closed") {// Not retryable as free text. Fall back to the approved template.return sendTemplate(order);}}
A 409 here is not transient
Retrying the same free-form send will fail identically until the customer writes to you again — which may be never. Backoff does not help. Queueing does not help. The only correct handling is to send a template instead, or to not send at all.
Nothing is sent automatically when the window closes #
For a message you send through POST /messages, there is no hold-and-release, no automatic re-engagement template, and no queue that drains when the customer next writes in.
When the window closes, the closed window is simply the state of the world. If you have something to say, you send a template. If you had a message pending in your own system, it is your system's job to notice the 409 and decide what happens next.
We are explicit about this because the alternative — quietly substituting a template you did not choose, on a Meta conversation category you did not budget for, to a recipient who may not want it — is worse than an error.
Ticket replies are the one exception, and they ask first
A staff reply typed on a ticket is held and released, because there the three objections above do not apply: the template is one you approved for this purpose, its conversation category is UTILITY, and nothing reaches the customer until they tap to ask for it.
The sequence is: the window is closed, so the customer receives a short notification template instead of the reply; tapping View update re-opens the window as a genuine inbound message; the held reply — and every other one waiting on that ticket, in the order it was written — is then delivered.
This never happens on the API path, and it never happens without a template your workspace has submitted and Meta has approved. See Working tickets.
The consequence for your design #
Work through the timing of a real notification and the conclusion is unavoidable.
A customer orders at 11 a.m. and asks a question in chat; the window opens. The order ships at 9 p.m. the next day — 34 hours later. The window closed 10 hours ago. Your "your order has shipped" message is a template, or it does not go out.
Now generalise. Anything triggered by your system rather than by the customer is almost always outside the window, because your systems run on your schedule and customers write on theirs:
| Flow | Triggered by | Realistically |
|---|---|---|
| Answering a support question | The customer, just now | Inside the window |
| Agent follow-up during a live chat | The customer, minutes ago | Inside the window |
| Order shipped / out for delivery | Your warehouse | Outside |
| Appointment reminder for tomorrow | A scheduler | Outside |
| Payment receipt, renewal notice | Your billing job | Outside |
| Feedback request a day later | A cron | Outside, by definition |
| Re-engaging a lapsed customer | A report | Far outside |
So: build every notification flow on templates from the start. Not as a fallback bolted on after the first 409 in production — as the primary path.
The pattern that survives contact with reality:
- 1
Get the template approved before you write the code
Template review takes minutes to a day. Doing it after your integration is built means your launch waits on Meta. See Templates.
- 2
Send the template unconditionally for anything system-triggered
Do not probe the window first. There is no endpoint that reports window state, and a template send works whether the window is open or closed. Checking would only add a failure mode.
- 3
Use free-form text only inside a conversation you are already in
An agent replying to a live conversation is inside the window by construction — the customer's message is what woke it up. That is where free text belongs.
- 4
Treat 409 / `session_window_closed` as "switch to a template", not as "retry later"
One code path, one decision, no queue.
What the agent does when it hits a closed window #
An agent can be given the ability to send a WhatsApp message — following up a web chat, for example. When it tries and the window is closed, it does not silently invent a substitute or apologise vaguely. The tool returns a structured result the model can act on, using these exact values:
sent · template_required · needs_setup · opted_out · template_not_allowed · template_not_found · template_unusable · needs_information · not_configured · failed
template_unusable means the template needs an image or video that hasn't been attached in the console, so the agent is steered to another allowed template.
template_required means the window is closed and the agent should reach for one of the templates it is permitted to send. Which templates those are is set on the agent's tool policy — the model cannot name a template you have not allowed. See Tools & actions.
Verifying the window is doing what you think #
The Verify a conversation card on a number's Overview tab, under Channels → WhatsApp, is a window test in disguise. It sends a free-form reply, so it only works inside the window.
- Message your business number from your phone. The window opens.
- Enter that number in Customer phone number and choose Send verification reply.
- "Your Amomic WhatsApp connection is working." arrives on your phone.
Skip step 1 and the send fails with the same error the API returns: "The 24-hour session window is closed; only approved template messages can be sent." That is the window rejecting a free-form send, end to end, in ten seconds.
The same card can also fail with "Recipient has opted out." if that phone number sent STOP to any of your numbers. Send START from it first.
Common misreadings #
Next #
Templates
How to write and submit one, what Meta approves, and how to fill its variables at send time.
POST /messages
The send contract — text, templates, sender selection, and every error code.
Order-shipped update
A complete flow built the right way round, template first.
Errors
The full error model, including how code and details.reason divide up.