Skip to main content
WhatsApp campaigns reach recipients over text and — for whatsapp_voice — escalate to a phone call when the conversation calls for it. This guide covers the message flow, the two timers that govern sends, templates, and inbound handling.

The whatsapp_voice flow

Each recipient runs through send → wait → decide → act:
1

Send template

The campaign re-checks opt-out status and required template variables, then sends the approved template. The provider receives only the template variables; Lehar renders the approved body into the stored transcript for the dashboard and the decision model.
2

Wait

Waits for a reply, a terminal delivery failure, or the response timeout — sending up to reminder_count template reminders while it waits.
3

Decide

A deterministic opt-out keyword check runs first (no model). Then an optional knowledge base lookup, then a structured LLM decision → call | reply | opted_out | no_call_required. Only this campaign’s messages feed the decision context, so past campaigns don’t leak in.
4

Act

call hands off to the outbound voice loop; reply sends an in-window free-form answer; opted_out / no_call_required / timeout finalize the recipient.

Two independent timers

Don’t conflate these — they govern different things.
  • 24-hour service window — whether a free-form message may be sent at all. It’s opened by the recipient’s last inbound message and is re-validated against the clock at send time. Outside the window, only approved templates may be sent.
  • response_timeout_seconds — the orchestration timer for how long to wait before a reminder or giving up. Independent of the window.
Reminders while waiting are templates (the window may not be open yet). The post-decision reply is free-form (the window is open because the recipient just replied).

Templates

WhatsApp sends use pre-approved templates from a customer-scoped registry. Administrators add approved templates per workspace; customers get a read-only view that feeds the dashboard dropdown and CSV columns. A template carries template_id, optional name, language, category, variables[], and an optional body_template (the approved body, rendered into internal transcripts). The campaign’s workflow.whatsapp config maps each template placeholder to a recipient variable or a literal fallback.

Configuration shape

whatsapp_voice config is stored on the campaign under workflow.whatsapp:

Inbound messages

Replies and delivery receipts arrive at POST /webhooks/whatsapp/inbound (provider-authenticated). Handling is idempotent on the provider’s message id and applies delivery statuses monotonically (a read is never regressed to delivered). An inbound reply opens the service window and wakes the waiting recipient. See Webhooks & Events.

Finalization states

A recipient finalizes as one of: completed, no_response, opted_out, no_call_required, or failed. An opted_out result also writes a workspace-level suppression so future campaigns honor it. The call branch finalizes through the reused voice path.

Reading conversations

GET /campaigns/{id}/events includes the structured campaign.whatsapp_decision entries (decision, reason, and reply text), which is the clearest way to audit why a recipient was called, answered, or suppressed.