The routing modes
“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.The sandbox number
The shared Wassist sandbox number (the one with no WhatsApp Business Account) supportsagent, webhook and null too, but routing is
per user: a change only applies to your own conversation with the
sandbox, never to other people’s. That makes it the quickest way to try
either mode. See the managed agent and
webhook routing quickstarts.
- Set sandbox routing from the dashboard: Numbers → sandbox number →
Routing. The phone-number endpoints also work with a signed-in user
session, but API keys get
400. A key belongs to the organization, not a person, so it has no sandbox conversation to route. - Sandbox conversations belong to your personal organization. Create the agent or webhook there, and keep it as the active organization when you change sandbox routing.
applyToExistingis ignored and no lifecycle events fire.- The service-window events aren’t sent for sandbox conversations.
How effective routing is resolved
In code terms:- 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:
The triggers are:
POST /conversations/{id}/subscribe/,/unsubscribe/, and/routing/(see below)
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 webhook must have the Subscription message received event enabled (it is by default). If it’s unticked, routed messages are dropped without a delivery.
- 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, plus routing
and webhookId:
subscription.activated, .revoked, the service
window pair) the message field is null — only conversation context is
sent.
Setting routing from your code
agent needs an agentId,
webhook needs a webhookId, and null (or "inherit") clears the
override:
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.- Routing changes on a sandbox number need a signed-in user; API keys get
400. The change only touches that user’s own sandbox conversation. - On your own numbers, only the organization that owns the WhatsApp
Business Account can change routing (
403otherwise). webhookIdmust reference a webhook in your organization.webhookIdis required when (and only when)mode === 'webhook'.