The routing modes
| Mode | What happens on inbound | When to use it |
|---|---|---|
agent | The connected agent replies as normal | Default for numbers wired to an agent. |
webhook | The message is forwarded to a single webhook; the agent is not run | When you want to handle replies in your own service. |
null | The message is stored, nothing else happens | Pause routing without disconnecting the agent or webhook. |
sandbox | Internal sandbox flow (shared system number) | System-managed only — see the note below. |
“No routing” is represented as
null on the wire — there is no "none"
string value. A number or conversation with defaultRouting: null (or
routing: null on the conversation) simply stores incoming messages and
does nothing else.sandbox is a system-managed mode for shared Wassist test numbers (numbers
with no WhatsApp Business Account). It lets you chat with yourself for
testing. You cannot set sandbox from the API or dashboard; any attempt to
do so returns 400. Sandbox numbers also ignore per-conversation routing
overrides.How effective routing is resolved
In code terms:- If the number is a sandbox number →
sandbox, full stop. - Otherwise, if the conversation has its own non-null
routing, use it. - Otherwise, fall back to the number’s
defaultRouting(which itself may benull— meaning “no routing”).
conversation.webhookOverride wins over whatsappNumber.defaultWebhook.
Subscription lifecycle events
Whenever a conversation’s effective(routing, webhook) pair transitions
to or away from a webhook, Wassist fires lifecycle events to the affected
webhook:
| Transition | Event |
|---|---|
| not-webhook → webhook W | subscription.activated on W |
| webhook W → not-webhook | subscription.revoked on W |
| webhook W1 → webhook W2 | subscription.revoked on W1, subscription.activated on W2 |
| same → same | (none) |
Phone-number-level routing changes (the
/phone-numbers/{n}/subscribe,
/connect-agent, and
/unsubscribe endpoints) do
not fire subscription lifecycle events, even when applyToExisting is
true. Number-wide changes are bulk admin actions — if your service needs
to know about every conversation, subscribe to it individually.Inbound message dispatch
Once a conversation is inwebhook routing:
subscription.message.receivedis dispatched only to the assigned webhook (no fan-out).- The agent pipeline is skipped entirely.
- The legacy
message.receivedevent still fan-outs to every active webhook subscribed to it, so existing integrations keep working.
Service window lifecycle
WhatsApp’s 24-hour customer service window is tracked per conversation inwebhook mode:
subscription.service_window.expiringfires roughly 1 hour before the window closes for a given conversation, once per window.subscription.service_window.closedfires when the window has closed.- The tracker resets the moment a new inbound user message lands.
Event payloads
Allsubscription.* events use the same envelope as
message.received, with two extra
fields:
subscription.activated, .revoked, the service
window pair) the message field is null — only conversation context is
sent.
Setting routing from your code
applyToExisting: true materialises the change onto every existing
conversation on this number — activeAgent is overwritten with the new
default (or cleared, for subscribe / unsubscribe) and any in-flight
session is dropped. No subscription lifecycle events are dispatched.
Webhooks must be created in the dashboard.
There is no API for webhook creation — the routing endpoints only let you
point conversations at webhooks that already exist.
Validation rules
mode='sandbox'is rejected on every public endpoint.- Any routing change on a sandbox number is rejected (the number is shared and system-managed).
webhookIdmust reference a webhook owned by the requesting user.webhookIdis required when (and only when)mode === 'webhook'.