WhatsApp auto‑reply.
WhatsApp is one of four channels in the unified messaging pipeline. Incoming WhatsApp messages are normalized into InboundMessage structs and processed through the common pipeline, which evaluates reply rules, generates the response, and logs metadata.
Adding a WhatsApp number
-
Open the admin
Navigate to Admin → WhatsApp Accounts → Connect WhatsApp Number.
-
Complete Embedded Signup
Meta’s in‑flow widget walks you through registering the phone number and attaching it to your shared WABA. The phone number and phone number ID are configured automatically when the flow returns.
-
Verify webhook
Concierge sends a test webhook to itself to confirm the round trip. If you see a green check on the account row, you’re live.
If you already know your phone number ID, use /admin/whatsapp/manual instead. Useful for migrating from another platform or when Embedded Signup isn’t available in your region.
Configuring auto-reply
Each WhatsApp account has its own ReplyConfig with two layers:
- Default reply: edited inline on the account’s edit page. Sets the fallback when no rule matches. Pick canned text or AI prompt.
- Reply rules: managed at
/admin/rules/whatsapp/{account_id}. An ordered list checked top‑down on every inbound message; first match wins.
| Field | Type | Description |
|---|---|---|
enabled | bool | Master switch; turns auto-reply off without losing rules. |
rules | Vec<ReplyRule> | Ordered list. First match wins. Each rule pairs a matcher (keyword substring or BGE‑embedding cosine similarity over a user‑written intent description) with a response (canned text or AI prompt). |
default_rule | ReplyRule | Mandatory fallback. Fires when no rule in rules matches. |
wait_seconds | u32 | 0–30s buffer; lets quick‑fire bursts collapse into one reply via the REPLY_BUFFER Durable Object. |
Canned response
Sent verbatim. No AI call, no credit charge. Good for hours/closed/FAQ replies.
{
"id": "hours",
"label": "Hours / location",
"matcher": {
"kind": "static_text",
"keywords": ["hours", "open", "closed", "address"]
},
"response": {
"kind": "canned",
"text": "We're open 7am–7pm every day. Come say hi! ☕"
}
}
AI prompt response
The matched rule’s prompt is concatenated to the tenant’s persona prompt (set at /admin/persona) and sent to Workers AI as the system instruction. The sender’s name and message are passed as user‑message context. One credit deducts before the call.
{
"id": "booking",
"label": "Booking requests",
"matcher": {
"kind": "prompt",
"description": "wants to book, reschedule, or check availability for an appointment",
"threshold": 0.72
},
"response": {
"kind": "prompt",
"text": "Ask which service and which day they prefer; mention a stylist will confirm shortly."
}
}
Reply generation uses @cf/meta/llama-4-scout-17b-16e-instruct. Prompt‑injection scanning + persona safety classification use the smaller @cf/meta/llama-3.1-8b-instruct-fast. Rule embeddings use @cf/baai/bge-base-en-v1.5. Latency is typically 600–1200ms; the customer sees it as “typing…” in WhatsApp.
Persona
The system prompt for every AI reply is the tenant‑wide persona prompt at /admin/persona (preset, builder, or custom) concatenated with the matched rule’s prompt. AI replies are blocked unless the persona’s safety status is Approved; canned default replies still send.
See Persona & safety check for the async classifier flow.
Credit deduction
Each AI‑mode reply (rule with a Prompt response) deducts one credit from the tenant’s balance. Canned responses are free. Credits are deducted before the AI call (optimistic) and restored if generation or send fails. When credits reach zero, AI replies stop silently; canned default replies still send.
See Billing for managing credits.
When you run out of credits, Concierge stops AI replies without notifying the customer. The inbound message is still logged. We do not send error messages to customers because that would itself cost money, and produce a bad impression.
Platform model
All WhatsApp numbers share a single platform token (WHATSAPP_ACCESS_TOKEN). This is a system user token for your WhatsApp Business Account. You don’t need per‑customer tokens. Customers add their phone numbers to your WABA via Meta’s Embedded Signup flow.
This means: one Meta app, one WABA, many phone numbers. The hosted service uses this same model. When you connect on concierge.calculon.tech, your number joins our WABA.
Message logging
Inbound and outbound message metadata (direction, sender ID, recipient ID, timestamp) is logged to the unified messages table. No message content is stored. The body lives in memory long enough to be passed to the AI and dispatched to the outbound API, then is dropped.
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id TEXT NOT NULL,
channel TEXT NOT NULL, -- whatsapp | instagram | email | discord
direction TEXT NOT NULL, -- inbound | outbound
sender_id TEXT NOT NULL,
recipient_id TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
-- intentionally no body, no subject, no attachments
);
Webhook payload reference
The Worker exposes the standard Meta WhatsApp webhook contract. GET /webhook/whatsapp handles verification (echoes hub.challenge when hub.verify_token matches WHATSAPP_VERIFY_TOKEN); POST /webhook/whatsapp ingests messages.
GET/webhook/whatsapp?hub.mode=subscribe…: verification handshakePOST/webhook/whatsapp: signed withX-Hub-Signature-256- verify
- HMAC‑SHA256 with
META_APP_SECRET· rejected if the signature is invalid - subscribed
messagesfield on the WhatsApp product