> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting: WhatsApp 24-hour session expired (WHATSAPP_SESSION_EXPIRED)

> Fix WHATSAPP_SESSION_EXPIRED on WhatsApp sends — Meta's 24-hour customer-service window closed and the free-form send was refused. Check the window before sending, fall back to an approved template, and re-engage the recipient.

# Troubleshooting: WhatsApp 24-hour session expired

A WhatsApp free-form send is refused with `WHATSAPP_SESSION_EXPIRED` when
Meta's 24-hour customer-service window for the recipient has closed. The
window opens when the recipient messages you and stays open for 24 hours
after their last inbound message; outside it, WhatsApp accepts template
messages only — any session (free-form) send is refused either by Orbit
before dispatch or by Meta further down the same chain
(`WHATSAPP_RE_ENGAGEMENT_WINDOW_CLOSED` / `WHATSAPP_RE_ENGAGEMENT_REQUIRED`,
then `WHATSAPP_OUTSIDE_24H_WINDOW`). The full window model is in the
[24-hour window guide](/guides/whatsapp/24h-window); this page is the fix
runbook for the error row you hit.

The gate is Meta-side and tenant-owned: it clears when the recipient
replies (a reply re-opens the window) or when you send an approved
template. Retrying the same free-form body changes nothing — pick the
remediation below instead. The code family sits in the
[Error Code Reference](/reference/error-codes).

## What the rejection looks like

The refusal comes back on the send call with the code on the error
envelope, and the same shape appears on failed rows in the Delivery
Log, on campaign recipient failures, and on `message.failed` webhook
payloads:

```json theme={null}
{
  "error": {
    "code": "WHATSAPP_SESSION_EXPIRED",
    "message": "WhatsApp 24-hour customer-service window has closed for this recipient; send an approved template"
  },
  "meta": {
    "request_id": "req_abc123",
    "timestamp": "2026-09-09T07:40:11Z"
  }
}
```

This gate is not a throttle: there is no `retry_after` in seconds the way
`RATE_LIMITED` carries one. No amount of waiting with the same free-form
body re-opens the window — **only a recipient reply re-opens it.** Time
helps exactly one way: waiting *for the recipient to answer*. Until then,
template messaging is the only path through. How re-engagement works is
the subject of the
[24-hour window guide](/guides/whatsapp/24h-window).

## Root causes

| Cause                                           | Check                                                                                                                      | Fix                                                                                                                 |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **Window lapsed, no pre-flight**                | The recipient's last inbound message is older than 24 hours and the send went straight to free-form without a window check | Call `GET /api/v1/messages/whatsapp/window-status` before composing; send a template when `within_window` is false  |
| **Free-form sent from a template-only state**   | The compose surface allowed a session send while the recipient sat outside the window                                      | Gate the UI on the window-status check so template-only recipients can only be reached by template                  |
| **Send attempted but account is template-only** | The recipient never messaged you (`no_inbound_history` on the window check), so no window ever opened                      | First-contact outreach must be an approved template — free-form is not permitted before the recipient's first reply |

The window-status endpoint and its `reason` values (`within_window`,
`window_expired`, `no_inbound_history`, `unknown`) are documented on the
[WhatsApp channel page](/channels/whatsapp#check-the-24-hour-window).

## Fixes

### 1. Gate the UI with the window-status check

Before you let an agent or integration compose a free-form message, check
whether the recipient is still inside the window:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/messages/whatsapp/window-status?to=+14155552671&from=+18005551234" \
  -H "X-API-Key: $ORBIT_API_KEY"
```

When the response reports `within_window: false`, switch the composer to
template-only instead of letting the send fail. The send pipeline
enforces the same check at dispatch; the pre-flight exists so the UI can
prevent the wasted attempt.

### 2. Fall back to an approved template

Outside the window, the only permissible content is an approved template.
Resend the substantive content as a template message, then treat the
recipient's reply as the trigger to resume free-form:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/messages/whatsapp \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+14155552671",
    "type": "template",
    "template": {
      "name": "customer_followup",
      "language": { "code": "en" },
      "components": [
        {
          "type": "body",
          "parameters": [
            { "type": "text", "text": "your question from earlier" }
          ]
        }
      ]
    }
  }'
```

If the template refuses because it has no variant for WhatsApp, author
the missing variant per the
[template-variant-missing runbook](/troubleshooting/template-variant-missing);
for rejected templates, work through the
[WhatsApp template troubleshooting page](/troubleshooting/whatsapp-template).
Template management and approval flow are under
**Channels → WhatsApp → Templates** and on the
[WhatsApp channel page](/channels/whatsapp#template-management).

### 3. Re-engage the recipient

Once the recipient replies to anything — a template, an earlier free-form
message, even a reaction — the window re-opens and free-form is accepted
again. Structure re-engagement templates so a reply comes naturally, and
time follow-ups inside the window rather than batching them hours later.

## What not to do

* **Do not retry the free-form send in a tight loop.** Every retry fails
  identically until the recipient replies — the error is deterministic,
  not transient, and the retry storm only pollutes your delivery metrics.
* **Do not treat this as a rate limit.** Backing off for a few seconds
  and resending is the wrong tool; there is no cooldown to wait out.
  Re-route: send a template, or move the content to a different channel
  with a cascade (SMS or Email under one `message_group_id` — see
  [Cascade failover policy](/concepts/message-cascade-groups)).
* **Do not send the same content on every channel at once.** Cascade
  fallback is ordered and conditional; a blast on every channel is spam
  the recipient can report, which degrades your WABA quality rating.

## Escalation criteria

Open a ticket with [support@devotel.io](mailto:support@devotel.io) when
the window-status pre-flight reports `within_window: true` and the
free-form send is still refused with this code, or when refusals persist
across every recipient rather than the lapsed ones. Include:

* The `request_id` from `meta` on the refusal.
* The `to` and `from` pair exactly as sent.
* The window-status response (endpoint output above) showing the
  mismatch.

If the refusal code moved between window-status and the send, that race
(recipient replied between check and send, or last-inbound aged out
mid-flight) is expected — re-check and the state will agree.

## See also

* [24-hour window guide](/guides/whatsapp/24h-window) — the window model
  and re-engagement rules
* [WhatsApp channel page](/channels/whatsapp) — window-status endpoint,
  templates, media, broadcast
* [Template variant missing](/troubleshooting/template-variant-missing) —
  when the fallback has no WhatsApp variant
* [WhatsApp template troubleshooting](/troubleshooting/whatsapp-template) —
  rejected, paused, or count-mismatched templates
* [Error Code Reference](/reference/error-codes) — the full
  `WHATSAPP_SESSION_EXPIRED` / re-engagement code family
* [Troubleshooting hub](/reference/troubleshooting-hub) — every runbook in
  one index
