WhatsApp Channel
From v3.5.0 Queria exposes a channel abstraction to let company bots receive and reply to messages on external messaging platforms. The first available implementation is Twilio for WhatsApp.
The abstraction is designed to host Telegram, SMS, Slack in later releases without refactoring the core.
User-side
For the user guide (number pairing, commands, usage examples) see WhatsApp Chat.
V1 operating model
| Aspect | V1 |
|---|---|
| Provider | Twilio (only) |
| Channel | WhatsApp (only) |
| Production number | Meta-approved shared number owned by Queria |
| Dev number | Twilio sandbox (whatsapp:+14155238886) |
| Tenant model | Shared Twilio account, optional tenant-specific fromAddress |
| Inbound | Text only (images/audio rejected with polite message) |
| Outbound | Replies to inbound only (no proactive notifications in V1) |
| Pairing | Code via web app, /pair <CODE> command from WhatsApp |
| Rate limit | 10 inbound messages / 60s per number |
Architecture
WhatsApp (user)
| inbound (Twilio webhook)
v
+------------------+ +------------------+ +-------------------+
| Twilio Webhook | ---> | Channel Inbound | ---> | runCanvasChat |
| Verify signature | | Service | | (CHAT canvas |
| Dedup | | - Pair commands | | whatsapp-default)|
| Rate limit | | - Resolve user | +-------------------+
+------------------+ | - Active conv | |
+------------------+ | response text
v
+------------------+
| TwilioChannel |
| Adapter.send |
| - Format WA MD |
| - Split 1500 ch |
| - REST send |
+------------------+
|
v
WhatsApp (user)The CHAT canvas executed on the inbound message is the same used for the web app, with the flag sys.channel = 'whatsapp' propagated. LLM components can read it and adapt the output (e.g. more compact language, omission of tabular blocks).
Global configuration (SYSTEM_ADMIN)
Required .env variables:
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TWILIO_AUTH_TOKEN=********
TWILIO_DEFAULT_FROM=whatsapp:+14155238886 # sandbox in dev
TWILIO_WEBHOOK_URL=https://admin.queria.pro/api/webhooks/twilio
# Pairing
CHANNEL_PAIR_CODE_TTL_MS=600000 # 10 minutes
CHANNEL_PAIR_RATE_LIMIT=3 # max 3 codes/hour per user
# Inbound rate limit
CHANNEL_INBOUND_RPS_BUCKET=10 # messages
CHANNEL_INBOUND_RPS_WINDOW=60 # secondsIn the Twilio provider panel:
- Messaging > WhatsApp Senders > Sandbox (dev) or Approved number (prod).
- Webhook URL =
https://admin.queria.pro/api/webhooks/twiliowith methodPOST. - Generate
AUTH_TOKENand set it in.env(never commit).
Per-tenant configuration
Admin panel > DSL & Pipelines > WhatsApp Channel.
| Field | Default | Editable by |
|---|---|---|
| Enabled | false | SYSTEM_ADMIN |
| fromAddress | Queria default | SYSTEM_ADMIN |
| Canvas pipeline | whatsapp-default | COMPANY_ADMIN (selection) |
| Auto-provision webhook | true | SYSTEM_ADMIN |
To assign a dedicated number to a tenant (Meta-approved):
- Purchase the number on Twilio Console.
- Enter
whatsapp:+<number>in the tenantfromAddressfield. - The backend calls the Twilio
IncomingPhoneNumbersAPI to pointSmsUrlat the Queria webhook. - A daily cron refreshes this configuration (30-day TTL).
Only SYSTEM_ADMIN may write fromAddress
COMPANY_ADMIN sees the number read-only in the company scheda. End users see the number in their profile so they know whom to message.
User pairing
A user does pairing once from their profile (see WhatsApp Chat - user side).
Internally:
ChannelPairCode-- table of generated codes (8 chars, 32-char alphabet, 10-min TTL).ChannelBinding-- final row per(channel, address)-> User. Globally unique.WebhookDedup-- dedup byproviderMessageIdto avoid double-processing on Twilio retries.
From the admin panel DSL & Pipelines > WhatsApp Channel > Bindings:
- Table of all tenant pairings (user, number, last message, total message count).
- Filters by user, date range, status.
- Ability to force unbind on
/stop(e.g. departing employee).
Handled commands
Intercepted before canvas execution:
| Command | Backend effect |
|---|---|
/pair <CODE> | ChannelPairCode lookup, TTL/used validation, ChannelBinding creation |
/reset | Closes the active Conversation (closedAt = now()); next message opens a new one |
/stop | Deletes ChannelBinding. Number released. Following messages rejected with "not recognized" |
/help | Replies with the list of commands |
Anything that is not a command flows through to the canvas.
Output formatting
The TwilioChannelAdapter post-processes the canvas response:
- Converts standard Markdown to WhatsApp-flavored markdown (
*bold*,_italic_,~strike~,`for code). - Adds a sources footer:
*Sources:* 1) Maintenance Contract 2026.pdf 2) Technical Annex A.docx - Hard-splits to 1500 characters per chunk (Twilio limit).
- Sends sequentially via
messages.create.
Logging and monitoring
Every inbound/outbound message creates records in:
ChannelMessageLog(Postgres) --providerMessageId,direction,latencyMs,statusCode.IngestionCallLogfor Twilio failures (e.g. number not opted in).- Prometheus metric (if enabled):
queria_channel_messages_total{provider,channel,direction,status}.
Admin page Channels > Monitoring: volume charts, latency, error rate, top users by messages.
Privacy and compliance
- Inbound messages are E2E encrypted by WhatsApp up to the Twilio gateway. Twilio-to-Queria traffic is HTTPS.
- Replies are subject to Meta's 24h customer service window: beyond 24h since the last interaction, an HSM template is required (V2).
/stopis the user-side opt-out. RemovesChannelBinding. TheConversationhistory stays unless explicit removal is requested (GDPR right to erasure).- Twilio data (Account SID, Auth Token) is stored encrypted in env vars; the admin UI never exposes them in clear.
Limits and roadmap
V1:
- Twilio provider only. Telegram/SMS/Slack supported at adapter level, not implemented.
- Text only inbound.
- User-initiated conversations only (no proactive push).
- One active conversation per (User, channel).
V2 (roadmap):
- HSM templates for proactive messages.
- Multi-tenant Twilio credentials (separate account per tenant).
- Telegram adapter as the second provider.
- Inbound multimedia (images sinking into documents).
Troubleshooting
"Code expired" -- the user waited longer than 10 minutes. Generate a new one.
"Number already paired" -- the target user must /stop first before re-pairing.
Webhook 403 -- check TWILIO_AUTH_TOKEN. HMAC-SHA1 signatures must match.
Message not answered -- check:
WebhookDedup(is it a duplicate?).- Rate limit (>10 msg/60s?).
- Canvas error (see
IngestionCallLog).
"For now I only accept text messages" -- the user sent media. V1 is text-only.
Queria v3.5.0 -- Channels (Twilio WhatsApp V1)