Documentation Index
Fetch the complete documentation index at: https://orbit-docs.devotel.io/llms.txt
Use this file to discover all available pages before exploring further.
WhatsApp 24h freeform window
Meta’s WhatsApp Business Platform splits outbound messages into two regimes:
- Inside the 24-hour customer-care window — your business may send any allowed content type (text, media, interactive buttons, lists, location). Free-form replies and ad-hoc support flows live here.
- Outside the 24-hour window — only pre-approved templates in one of Meta’s four conversation categories (Marketing, Utility, Authentication, Service) may be sent.
The window opens (or re-opens) every time the customer sends an inbound message to your WABA phone number. From the timestamp of that inbound message you have 24 hours to send free-form replies; the next inbound message resets the clock.
This page covers how to check the window status pre-flight, the platform’s send-time enforcement, and how the dashboard surfaces the rule.
How the window opens / closes
| Event | Effect on the window |
|---|
| Customer sends a message to your WABA number | Window opens for 24 hours. Subsequent inbound messages reset the 24h clock. |
| You send a template message | Window does NOT open — only an inbound from the customer can do that. |
| 24 hours elapse without a new inbound | Window closes. Free-form sends will be rejected. |
The window is keyed on the (customer_phone, your_whatsapp_business_phone_number) pair. A customer who has messaged your +1-800 number does NOT open the window for your +44-7700 number.
Check the window status pre-flight
The dashboard’s compose dialog calls this endpoint as the operator types — the BE is the authoritative source, but the FE pre-flight avoids a wasted send-attempt round-trip.
curl -G https://api.orbit.devotel.io/api/v1/messages/whatsapp/window-status \
-H "X-API-Key: dv_live_sk_..." \
--data-urlencode "to=+14155552671" \
--data-urlencode "from=+18005551234"
from is optional. When supplied, the lookup is scoped to inbound from to directed at the specific from number — matches the per-WABA scoping the send route enforces. When omitted, any inbound from to to any of your WABA numbers counts.
Response
{
"data": {
"within_window": true,
"last_inbound_at": "2026-05-09T08:42:11Z",
"hours_since_last_inbound": 4,
"reason": "within_window"
},
"meta": { "request_id": "req_xyz789", "timestamp": "2026-05-09T12:42:11Z" }
}
reason values
| Value | Meaning |
|---|
within_window | The contact has messaged you within the last 24 hours; free-form sends are allowed. |
window_expired | The contact has messaged you in the past, but the most recent inbound is older than 24 hours. Send a template. |
no_inbound_history | We have no inbound history for this (to, from) pair. Templates only. |
Send-time enforcement
The send route (POST /api/v1/messages/whatsapp) re-runs the window check server-side and refuses free-form sends that would break Meta’s rule:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": {
"code": "WHATSAPP_OUTSIDE_WINDOW",
"message": "The 24-hour customer-care window has expired. Use a template or wait for the customer to message first."
}
}
Template sends (type=template) bypass the window check — they are always permitted, subject to the template’s own approval status and category.
Dashboard signals
In the inbox conversation header and the WhatsApp compose dialog the dashboard renders one of four states:
| State | Compose behaviour |
|---|
| Window open, > 1h remaining | Free-form composer enabled; subtle “open until ” badge. |
| Window open, < 1h remaining | Free-form composer enabled; orange countdown. |
| Window closed | Free-form composer disabled; template picker is the only option. |
| No history | Free-form composer disabled; template picker required. |
The countdown ticks once per minute via the dashboard’s shared 1Hz tick — no per-row clocks.
Templates approved for outside-window use
Templates are categorised by Meta when you submit for approval:
| Category | Use case | Example |
|---|
MARKETING | Promotional / campaign content. | ”Spring sale — 20% off all stock until Sunday.” |
UTILITY | Transactional updates. | "Your order ORD-12345 has shipped — track it here: {{1}}" |
AUTHENTICATION | OTP / 2FA codes. | "Your verification code is {{1}}." |
SERVICE | Customer service responses. | "Hi {{1}}, our team is reviewing your case and will respond within 24 hours." |
Pricing varies by category — see the WhatsApp pricing guide.
Common patterns
Re-engagement template
If you regularly need to reach customers outside the window, build an approved Marketing or Utility template that re-opens the conversation:
# Step 1: send the template (allowed outside the window)
curl -X POST https://api.orbit.devotel.io/api/v1/messages/whatsapp \
-H "X-API-Key: dv_live_sk_..." \
-H "Content-Type: application/json" \
-d '{
"to": "+14155552671",
"type": "template",
"template": {
"name": "back_in_stock",
"language": { "code": "en" },
"components": [{ "type": "body", "parameters": [{ "type": "text", "text": "Air Max 90" }] }]
}
}'
# Step 2: customer replies → window opens → you can send free-form
Branching on window status
import { Devotel } from "@devotel/orbit-sdk";
const orbit = new Devotel({ apiKey: process.env.ORBIT_API_KEY! });
const status = await orbit.messages.whatsappWindowStatus({
to: "+14155552671",
from: "+18005551234",
});
if (status.data.within_window) {
await orbit.messages.send({
channel: "whatsapp",
to: "+14155552671",
from: "+18005551234",
type: "text",
text: { body: "Thanks for your patience — your order shipped today!" },
});
} else {
await orbit.messages.send({
channel: "whatsapp",
to: "+14155552671",
from: "+18005551234",
type: "template",
template: {
name: "order_shipped",
language: { code: "en" },
components: [{ type: "body", parameters: [{ type: "text", text: "ORD-12345" }] }],
},
});
}
See also