Skip to content

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

AspectV1
ProviderTwilio (only)
ChannelWhatsApp (only)
Production numberMeta-approved shared number owned by Queria
Dev numberTwilio sandbox (whatsapp:+14155238886)
Tenant modelShared Twilio account, optional tenant-specific fromAddress
InboundText only (images/audio rejected with polite message)
OutboundReplies to inbound only (no proactive notifications in V1)
PairingCode via web app, /pair <CODE> command from WhatsApp
Rate limit10 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:

bash
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     # seconds

In the Twilio provider panel:

  1. Messaging > WhatsApp Senders > Sandbox (dev) or Approved number (prod).
  2. Webhook URL = https://admin.queria.pro/api/webhooks/twilio with method POST.
  3. Generate AUTH_TOKEN and set it in .env (never commit).

Per-tenant configuration

Admin panel > DSL & Pipelines > WhatsApp Channel.

FieldDefaultEditable by
EnabledfalseSYSTEM_ADMIN
fromAddressQueria defaultSYSTEM_ADMIN
Canvas pipelinewhatsapp-defaultCOMPANY_ADMIN (selection)
Auto-provision webhooktrueSYSTEM_ADMIN

To assign a dedicated number to a tenant (Meta-approved):

  1. Purchase the number on Twilio Console.
  2. Enter whatsapp:+<number> in the tenant fromAddress field.
  3. The backend calls the Twilio IncomingPhoneNumbers API to point SmsUrl at the Queria webhook.
  4. 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 by providerMessageId to 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:

CommandBackend effect
/pair <CODE>ChannelPairCode lookup, TTL/used validation, ChannelBinding creation
/resetCloses the active Conversation (closedAt = now()); next message opens a new one
/stopDeletes ChannelBinding. Number released. Following messages rejected with "not recognized"
/helpReplies 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:

  1. Converts standard Markdown to WhatsApp-flavored markdown (*bold*, _italic_, ~strike~, ` for code).
  2. Adds a sources footer:
    *Sources:*
    1) Maintenance Contract 2026.pdf
    2) Technical Annex A.docx
  3. Hard-splits to 1500 characters per chunk (Twilio limit).
  4. Sends sequentially via messages.create.

Logging and monitoring

Every inbound/outbound message creates records in:

  • ChannelMessageLog (Postgres) -- providerMessageId, direction, latencyMs, statusCode.
  • IngestionCallLog for 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).
  • /stop is the user-side opt-out. Removes ChannelBinding. The Conversation history 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:

  1. WebhookDedup (is it a duplicate?).
  2. Rate limit (>10 msg/60s?).
  3. 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)

Queria - Document Intelligence con Cog-RAG