Skip to content

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.

Open WhatsApp settings

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 #

The WhatsApp 24-hour windowWindow open — 24 hoursfree-form text and templates both sendWindow closedapproved templates onlyCustomer messages youthe clock starts (and restarts) here+24hSend free-form text after the window closes and the API answers 409 withsession_window_closed— send an approved template instead.
The window opens on the customer's message and closes 24 hours later. Your replies do not extend it — only theirs do.

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:

409conflictsession_window_closedThe 24-hour session window is closed; only approved template messages can be sent.
409 Conflict
{
"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.

Handling it properly
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. 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. 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. 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. 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.

  1. Message your business number from your phone. The window opens.
  2. Enter that number in Customer phone number and choose Send verification reply.
  3. "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 verification reply refused because the customer hasn't written in the last 24 hours.

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.