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

# Chat

> Vorel's chat agent: WhatsApp inbound today; the same router → sub-agent dispatch as voice; cross-channel customer continuity by phone number.

Vorel's chat agent handles inbound WhatsApp conversations end-to-end. Same router → sub-agent shape as voice; same per-tenant persona, vertical pack, and guardrails. Customer identity binds across voice + chat by phone number.

## What chat does today

<CardGroup cols={2}>
  <Card title="WhatsApp inbound" icon="message">
    Customers message your business's WhatsApp number. Our WhatsApp provider forwards the message to
    our webhook receiver; the worker queues it; the agent replies through the same dispatch path
    voice uses.
  </Card>

  <Card title="Cross-channel context" icon="link">
    A customer who called and now WhatsApps continues the same conversation. `customers.phone` is
    the identity anchor; `conversations` are looked up by `(tenant, channel, customer_identifier)`
    with phone-number normalization across `wa_id` and E.164 forms.
  </Card>

  <Card title="Bilingual (Arabic + English)" icon="language">
    Same prompt-driven language detection as voice. The agent responds in the language the customer
    used on the latest turn.
  </Card>

  <Card title="Operator inbox reply" icon="user-pen">
    Your team can take over a thread from `app.vorel.ai/inbox`; the agent stops auto-replying once
    a human reply is sent on that conversation. Re-enable auto-reply via the inbox toggle.
  </Card>

  <Card title="Webhook events" icon="webhook">
    Subscribe to `lead.created`, `lead.qualified`, `conversation.created`,
    `conversation.handoff_requested`, `conversation.closed`: fire-on-state-change for your own
    integrations. Bodies are HMAC-SHA256 signed. Six-attempt retry ladder.
  </Card>

  <Card title="Dashboard send" icon="paper-plane">
    The dashboard reply path uses the same `send_whatsapp_message` tool; your operator's reply is
    persisted into `messages` and lands in the conversation transcript.
  </Card>
</CardGroup>

## What's paused

<Note>
  **WhatsApp outbound network send is being finalized** as Meta Business Manager verification clears
  per tenant. The `send_whatsapp_message` tool persists the agent reply to the conversation
  transcript and writes the outbox row; the network send to your WhatsApp provider activates
  per-tenant once verification is through. Inbound messages flow normally; the dashboard inbox shows
  everything.
</Note>

In the meantime, a customer's message lands in your inbox and an agent reply is generated and
persisted, and your team can take over the conversation manually from the inbox.

## How a chat turn flows

```
Customer WhatsApp
    ↓
WhatsApp provider
    ↓
POST /api/webhooks/whatsapp  (HMAC-verified, IP rate-limited 500/min)
    ↓
BullMQ → message-processor queue
    ↓
worker:
  1. Persist customer turn into `messages` (with hallucination_flags=null)
  2. Upsert `customers` row by phone-number
  3. Customer-number rate limit (30 events/min per (tenant, phone))
  4. POST /api/internal/agent/dispatch  (signed worker JWT, 2-min TTL)
    ↓
runChatDispatch:
  1. Hydrate context (history + customer + persona + vertical + pack overrides)
  2. classifyIntent → one of ~10 intent slugs
  3. Sub-agent (qualification / FAQ / booking / handoff) with its tool whitelist
  4. send_whatsapp_message terminal: persist agent_bot turn + grade hallucinations + outbox
```

Voice and chat run the **same** `runChatDispatch` / `runVoiceDispatch` shape internally; only the ingress and the terminal differ. See [How it works](/getting-started/how-it-works) for the full picture.

## Tool whitelist by sub-agent (chat)

| Sub-agent       | Tools                                                                                                |
| --------------- | ---------------------------------------------------------------------------------------------------- |
| `qualification` | `search_offerings` · `update_lead` · `crm_lookup_customer` · `crm_update_record` · `request_handoff` |
| `booking`       | `check_availability` · `book_appointment` · `update_lead` · `request_handoff`                        |
| `handoff`       | `request_handoff`                                                                                    |
| `faq`           | `get_faq_answer` · `search_offerings` · `crm_lookup_customer` · `request_handoff`                    |

Per-tenant tool whitelists trim this further: your operator can disable `crm_update_record` for a tenant whose CRM is read-only, etc.

## Channel-aware reply formatting

Chat replies use a different output style than voice. The channel-rules block appended to every chat sub-agent prompt allows:

* Up to four sentences per turn (vs. voice's two-sentence cap).
* Bullet / numbered lists when listing options or steps.
* Numerals are readable (`AED 1,200,000`, no need to spell out).
* Emojis sparingly OK for warmth (one per message at most; never as decoration).
* Links are fine when relevant; the customer can tap them.

These rules are appended to every sub-agent prompt by a channel-rules block; the prompts themselves are channel-neutral.

## Anti-spam posture

Inbound abuse is gated at three layers:

1. **Per-IP webhook limit (500 req/min)**: applied before signature verification, so a flood from a single IP doesn't compromise the verification path's cost.
2. **HMAC signature verification**: the WhatsApp provider signs every webhook with a shared secret; we reject on signature mismatch with a 401 and a `webhook.signature_invalid` log line.
3. **Per-(tenant, customer phone) limit (30 events/min)**: applied after parse, so a single compromised customer number can't burn through the tenant's quota or generate bot work.

Sustained 30/min from a single number is suspicious; the agent path goes silent and the operator inbox surfaces a `customer_rate_limited` flag.

## Webhooks emitted on chat events

Subscribe at `/(dashboard)/settings/integrations/webhooks`. Chat-relevant events:

| Event                            | When                                                  |
| -------------------------------- | ----------------------------------------------------- |
| `conversation.created`           | First inbound from a new (tenant, customer) pair      |
| `lead.created`                   | Qualification sub-agent created the lead              |
| `lead.updated`                   | Slot changes via `update_lead`                        |
| `lead.qualified`                 | Lead status moved off `'new'`                         |
| `conversation.handoff_requested` | Handoff sub-agent fired or `request_handoff` tool ran |
| `conversation.closed`            | Inbox closed by an operator action                    |

Webhook bodies carry `{id, event, created_at, tenant_id, data}`; `id` doubles as the consumer's idempotency key. HMAC-SHA256 signature in `X-Webhook-Signature`. Retry ladder: `[60s, 300s, 1800s, 7200s, 43200s]` with a 6-attempt cap; failures past 6 attempts raise a tenant-admin alert and land in `webhook_deliveries.status='dead_letter'`.

## What's NOT supported on chat today

* **Outbound network send to WhatsApp**: paused per the note above.
* **Outbound proactive messaging (campaigns)**: depends on Phase 4b; even after that lands, outbound campaigns are scoped to opted-in customers per Meta policy.
* **Other messaging channels (Telegram, Instagram DM, SMS)**: WhatsApp only today.

## Per-tenant configuration that matters for chat

Set via the operator console, no code deploy:

* **Persona**: agent name, tone, signature phrases, forbidden phrases (Arabic + English).
* **Working hours + holidays**: rejects outside-hours inbound at the agent layer with a per-vertical "we're back at" message; falls through to a human if your handoff rules say so.
* **Vertical pack**: drives the qualification slots, FAQ redirect copy, handoff triggers (see [Verticals](/verticals/real-estate)).
* **Tool whitelist**: disable `crm_update_record` (read-only CRM), `book_appointment` (no online booking yet), etc.
* **Guardrails**: hallucination threshold + action; forbidden-phrase action. See [Guardrails](/product/guardrails).

## Related docs

* [How it works](/getting-started/how-it-works): full architecture
* [Voice features](/product/voice): voice side of the same agent
* [Verticals](/verticals/real-estate): per-vertical qualification + handoff defaults
* [Guardrails](/product/guardrails): per-tenant safety policy
* [API Reference](/api-reference/introduction): webhook subscription, conversation send

{/* verified-against: apps/workers/src/queues/message-processor.ts (BullMQ → /api/internal/agent/dispatch) */}

{/* verified-against: apps/web/src/app/api/tools/send_whatsapp_message/route.ts (persists reply + outbox row; network send to WhatsApp provider gated on per-tenant Meta verification) */}

{/* verified-against: apps/web/src/lib/rate-limit.ts rateLimitByCustomerNumber (30 req/min default) */}
