Communication Channels
Configure SMS, WhatsApp, and generic-webhook channels, monitor channel health, and understand which channels are fully live today.
Owlat models every conversation medium — email, native chat, SMS, WhatsApp, and a catch-all webhook — as a channel. Inbound messages from all of them land in the shared Team Inbox on a unified timeline. This page covers the Channels settings screen where owners and admins enable channels, store provider credentials, and watch health status.
Email and native chat send and receive end-to-end. Owlat can also send outbound over SMS, WhatsApp, and the generic webhook, but only as an AI-agent reply (auto-send or approved draft) when the AI agent (ai.agent) is enabled. An owner/admin can also manually send over SMS, WhatsApp, or the generic webhook from a contact's Unified Timeline (Contacts page) using the per-contact composer; there is no per-message reply button inside the Team Inbox itself. See Current limitations.
Where to find Channels
Open Settings → Channels in the dashboard (route /dashboard/settings/channels). The page itself only requires being signed in, but changing anything — enabling/disabling a channel or saving credentials — is restricted to owners and admins: the underlying updateChannelConfig mutation requires the organization:manage permission (see Team & Permissions). A non-admin who reaches this screen can read the channel list but their save and toggle actions are rejected.
The screen lists one card per channel. Each card shows the channel's icon and name, an enable/disable toggle, a health badge, and a Configure panel that expands to a credential form. A sidebar summarises totals: how many channels exist, how many are enabled, and how many are healthy, degraded, or down.
What the channels are
| Channel | What it does today |
|---|---|
| Send and receive via the built-in MTA. Fully live. No credentials to enter here — email delivery is managed by the MTA. | |
| Chat | Native in-app chat powered by the built-in messaging system. Fully live. No credentials required. |
| SMS | Receive inbound texts via Twilio. Outbound send works only via AI-agent replies (no manual reply UI), when the AI agent is enabled. |
| Receive inbound messages via the WhatsApp Business (Meta Cloud) API. Outbound send works only via AI-agent replies (no manual reply UI), when the AI agent is enabled. | |
| Generic webhook | Receive inbound messages from any HTTP source via a shared secret. Outbound send works only via AI-agent replies (no manual reply UI), when the AI agent is enabled. |
What "enabled" really means
The enable toggle controls whether a channel is included in the periodic health check and surfaced as active in the overview counts. For email and chat it reflects a fully working channel. For SMS, WhatsApp, and the generic webhook, the toggle only governs health monitoring and the overview — it does not turn on outbound sending, and it does not gate inbound: inbound webhooks are accepted purely on a valid signature plus the matching backend secret (see Inbound channel webhooks), whether or not the channel is toggled on.
Configuring SMS, WhatsApp, and the generic webhook
Click Configure on a channel card to open its form. Every channel accepts an optional Display Name (shown in the UI in place of the default channel name). Beyond that, the credential fields differ per channel.
The form fields below are display/context for inbound verification only — inbound webhook verification secrets are read from Convex environment variables, not from these form fields, so the environment variables (not the form) are what make inbound requests succeed. The same form fields are used for outbound dispatch, though: they are encrypted at rest (channels.outbound.encryptAndPersistConfig) and decrypted by channels.outbound.dispatchOutbound when the AI agent replies on the channel. See Inbound channel webhooks.
Configure SMS (Twilio)
Enter your Twilio credentials:
| Field | Example |
|---|---|
| Account SID | ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
| Auth Token | your Twilio auth token (masked in the form) |
| Phone Number | +1234567890 |
For inbound SMS verification, also set the TWILIO_AUTH_TOKEN environment variable on the Convex backend.
Configure WhatsApp (Meta)
Enter your WhatsApp Business / Meta Cloud API credentials:
| Field | Description |
|---|---|
| Business Account ID | your WhatsApp Business Account ID |
| Access Token | Meta Cloud API access token (masked in the form) |
| Phone Number ID | the phone number ID from Meta |
For inbound WhatsApp, also set META_APP_SECRET (for signature verification) and META_VERIFY_TOKEN (for Meta's subscription challenge) on the Convex backend.
Configure the generic webhook
| Field | Description |
|---|---|
| Endpoint URL | https://example.com/webhook — the destination Owlat POSTs AI-agent replies to (via WebhookAdapter) |
| Secret Key | a shared secret, sent as the outbound signing secret |
For inbound generic messages, set the GENERIC_WEBHOOK_SECRET environment variable, which Owlat compares against the incoming request's secret.
Click Save Configuration to persist. A toast confirms the save. Token fields are masked by default with a show/hide toggle.
Channel health monitoring
A background cron runs every 5 minutes and updates the health status of each enabled channel. Each card shows a coloured badge plus, when available, the time of the last check, the last successful send, and the last error.
| Status | Meaning |
|---|---|
| Healthy (green) | The channel passed its last check. |
| Degraded (amber) | The channel is reachable but reporting problems. |
| Down (red) | The channel failed its last check. |
| Disabled / Unknown | The channel is turned off, or no check has run yet. |
Email and chat are always reported as healthy by this cron (email deliverability is tracked separately by the provider-health system, see Deliverability). For SMS, WhatsApp, and the generic webhook, the periodic check now makes a live provider probe for configured channels — a configured SMS channel fetches the Twilio account and a configured WhatsApp channel fetches the Meta graph, so revoked or invalid credentials report degraded or down. An unconfigured channel still reports down with "No credentials configured". Only the generic webhook remains credential-presence only.
A separate poll channel delivery status cron runs every 5 minutes so outbound SMS, WhatsApp, and generic messages do not sit at sent forever: it polls the provider for each message accepted in the last 24 hours and advances it to delivered, read, or failed on the contact's unified timeline as the carrier reports back.
Inbound channel webhooks
This is where messages from SMS, WhatsApp, and generic sources arrive. Point your provider at the matching Owlat HTTP endpoint:
| Channel | Endpoint | Method |
|---|---|---|
| SMS (Twilio) | /webhooks/sms | POST |
| WhatsApp (Meta) | /webhooks/whatsapp | POST (inbound), GET (Meta verification challenge) |
| Generic | /webhooks/channel | POST |
Every inbound request is verified before it is accepted — the channel webhooks fail closed if their secret is not configured:
- Twilio requests are verified with an HMAC-SHA1 signature over the canonical URL-plus-sorted-params string (
X-Twilio-Signature), usingTWILIO_AUTH_TOKEN. - Meta (WhatsApp) requests are verified with an HMAC-SHA256 signature of the raw body (
X-Hub-Signature-256), usingMETA_APP_SECRET. TheGETchallenge is answered usingMETA_VERIFY_TOKEN. - Generic requests are authenticated with a constant-time comparison of a shared secret supplied via the
X-Webhook-SecretorAuthorization: Bearerheader, againstGENERIC_WEBHOOK_SECRET.
When a verified message arrives, Owlat finds or creates a contact and a conversation thread for it and stores the message on the unified timeline, where it appears in the Team Inbox. Each channel is its own identity space: an SMS sender and an email contact with the same value are not automatically the same contact — contacts reached over SMS, WhatsApp, or generic channels may have no email address at all. See Audience Data for how identities are resolved.
For the exact request shapes, headers, verification rules, and response codes, see Inbound Channel Webhooks.
Current limitations
Be precise about what these channels do today:
- Outbound send over SMS, WhatsApp, or generic only happens through the AI agent. The full path is real and invoked: an inbound channel message runs the AI agent (
channels.tsprocessInboundChannel→agent.walker→agentPipeline.sendApprovedReply→channels.outbound.dispatchOutbound), which builds the real provider adapter (Twilio, Meta, or a downstream webhook) and sends. It is gated on theai.agentfeature flag and on the channel config being enabled with credentials. In addition to the AI-agent reply path, an owner/admin can manually send on a configured channel from the contact's Unified Timeline (channels.outbound.sendChannelMessage); there is just no per-message reply button inside the Team Inbox itself. - Signatures are not enforced as a per-channel setting. Inbound verification is real and fails closed, but it is keyed off the backend environment variables above, not off the credentials you type into the form. Those form credentials are, however, used for the outbound path: they are encrypted at rest and decrypted by
channels.outbound.dispatchOutboundwhen the AI agent replies on the channel. - Health checks are credential-presence only for the generic webhook; SMS and WhatsApp now run a live provider
healthCheck()probe (Twilio account / Meta graph) for configured channels.
Use these channels to receive and triage messages in the Team Inbox; outbound over SMS/WhatsApp/generic happens through the AI agent or a manual send from the contact's Unified Timeline, since there is no per-message reply button inside the Team Inbox itself.