Social channels — Messenger, Instagram, Telegram, Discord, SMS, Apple, LINE, Viber
- Written for
- + Written for
- Deprecated
- + Deprecated
- Applies to
- + Applies to
Social channels — Messenger, Instagram, Telegram, Discord, SMS, Apple, LINE, Viber
One inbox, every social DM. All BYOK access tokens — Chatly never owns your audience.
Every social channel in Chatly follows the same pattern: you bring the access token from the platform's developer console, paste it into a new channel, and we handle webhooks, threading, attachments, and outbound.
The credentials remain yours — if you ever stop using Chatly, you keep your Page, your bot, your phone numbers, your audience.
Info — One inbound URL for every channel
There are no per-provider ingest paths. Every provider that delivers over HTTP posts to
/v1/channels/webhook/{publicId}, wherepublicIdis the id you chose when you created the channel. The channel detail screen prints the exact URL for copy-paste — use that, not a hand-written one.
POST /v1/channels/webhook/{publicId} — Public
GET /v1/channels/webhook/{publicId} — Public
The GET handler exists purely for Meta's hub.challenge handshake and Slack's url_verification POST is answered before any channel lookup, so both platforms can save a Request URL while you are still configuring.
Every channel below produces the same conversations + messages rows. Routing, AI, RAG, copilot and reports are channel-agnostic; the channel-specific logic lives in apps/api/src/modules/channels/adapters and is invisible to the rest of the app.
Warning — Creating a channel needs a publicId you choose
POST /v1/channelsrequirespublicId(1–64 characters, unique across the deployment) and rejects a body without one. It cannot be changed afterwards, because it is part of your webhook URL. Saving a channel never test-calls the provider — a wrong credential looks connected until traffic arrives.
Facebook Messenger
Setup
Create a Facebook App and connect it to a Page you own in Meta Business Manager.
Subscribe the Page to the
messageswebhook field.Generate a Page Access Token (long-lived, via a system user).
Channels → New → Messenger. All four fields are required:
Parameters
Name | Type | Description |
|---|---|---|
|
| Facebook Page ID. |
|
| Long-lived Page access token. |
|
| Verifies the |
|
| A random string you choose. Type the same value into Meta's Callback URL form. |
Webhook
In Meta App → Webhooks → Page → Callback URL:
https://api.chatlychat.com/v1/channels/webhook/{publicId}Signature failures are rejected with 403, verified over the exact bytes Meta signed.
The 24-hour window
Messenger enforces a 24-hour rolling window after the user's last interaction, and outside it Meta requires a message tag.
Warning — Chatly does not send message tags
Outbound Messenger sends are hardcoded to
messaging_type: 'RESPONSE'. There is no tag field, noHUMAN_AGENTsupport, and no composer prompt for one — a send outside the window will simply be refused by Meta.
Instagram DMs
Instagram Direct rides the Meta Graph API, so this is set up exactly like Messenger — through the linked Facebook Page.
Setup
Your Instagram account must be a Business or Creator account linked to a Facebook Page.
Required permissions on the Meta App:
instagram_basic,instagram_manage_messages,pages_messaging.Channels → New → Instagram. The fields are the same four as Messenger — note the first one:
Parameters
Name | Type | Description |
|---|---|---|
|
| The linked Facebook Page ID, not the Instagram Business Account ID. |
|
| Token for the Page that owns the IG account. |
|
| Webhook signature verification. |
|
| Your chosen handshake string. |
Same callback URL shape as Messenger. Inbound Instagram messages are stamped with the instagram channel type (not messenger), so the Instagram inbox filter and Instagram reporting see them.
Info — What is not wired
Story replies, ice-breakers, mentions and comment-to-DM automations are Meta webhook fields the adapter does not parse. Only direct messages become conversations.
Telegram
The simplest social channel — no business approval, no 24-hour window.
Setup
Open Telegram, message @BotFather, send
/newbot. Save the token.Channels → New → Telegram:
Parameters
Name | Type | Description |
|---|---|---|
|
| From BotFather's reply to |
|
| e.g. |
|
| Optional — leave blank and Chatly generates one. Telegram echoes it on every delivery, which is how we know a request really came from Telegram. |
On save, Chatly calls Telegram's
setWebhookfor you, pointing at/v1/channels/webhook/{publicId}with the secret token andallowed_updates: ["message", "edited_message", "callback_query"]. The result rides back on the API response, so a failure to register is visible immediately rather than as permanent silence. Re-saving retries.
Info — Telegram bots are BYOK, and so is the userbot
There is no platform-level Telegram credential. The bot channel uses your bot token. The separate Telegram user channel (MTProto) is also bring-your-own: there is no platform
TELEGRAM_API_IDorTELEGRAM_API_HASH— theapi_id/api_hashpair lives per-workspace onintegration_installs.
What works
1:1 DMs to the bot.
Group messages, subject to BotFather's privacy setting.
Edited messages and callback-query taps.
Discord
Warning — Discord does not deliver over HTTP
Discord is the one channel that does not use the ingest route. Messages arrive over a WebSocket held open by a separate service,
apps/discord-gateway. If that service is not running, a Discord channel is configured, shows no error, and receives nothing.
Setup
Discord Developer Portal → New Application → Bot.
Enable the MESSAGE CONTENT privileged intent on the application. Without it Discord delivers every message with an empty
content, and the bot appears to receive nothing useful.Add the bot to your server with the OAuth2 URL Generator: scope
bot, with Read Messages and Send Messages.Channels → New → Discord — or click Connect to authorise through the built-in Discord OAuth flow:
Parameters
Name | Type | Description |
|---|---|---|
|
| The server (guild) ID, not the application ID. Turn on developer mode in Discord, right-click the server, Copy Server ID. |
|
| From your bot application in the developer portal. |
|
| Ed25519 key used to verify interaction-endpoint signatures. |
What works
The gateway subscribes to GUILD_MESSAGES, DIRECT_MESSAGES and MESSAGE_CONTENT, and files every non-bot message that has content. Conversations are keyed on the Discord channel_id, so a thread is its own conversation. Outbound goes to POST /channels/{channelId}/messages on Discord's v10 API.
Info — Slash commands are not routed
The adapter can verify an interaction signature, but only
MESSAGE_CREATEis turned into a conversation. Slash commands and component interactions are dropped.
SMS
BYOK carrier — Twilio, Telnyx or SignalWire. The SMS channel carries its own credentials; it does not share them with a Voice channel, and the two store them in different shapes.
Parameters
Name | Type | Description |
|---|---|---|
|
|
|
|
| E.164 sender. |
|
| Twilio. |
|
| Telnyx. |
|
| SignalWire — the API is rooted at your own space, not at api.twilio.com. |
Point your carrier's inbound SMS webhook at the shared ingest URL:
https://api.chatlychat.com/v1/channels/webhook/{publicId}Twilio's signature is verified against the public URL rebuilt from the proxy's forwarding headers, so a deployment behind Traefik or a load balancer verifies correctly. The receiver answers 200 rather than Nest's default 201, because Twilio logs error 12300 on anything else.
MMS carries images and video only. Audio and documents are sent as a link in the message body instead — Twilio's MMS does not accept them.
Warning — A2P 10DLC + TCPA — your problem, not ours
US SMS senders must register their brand + use case with The Campaign Registry for A2P 10DLC. TCPA requires written consent for marketing SMS. Chatly records per-channel consent on
contact_channel_consent— and for SMS, like WhatsApp, no row means refused — but complying with the law is your responsibility.
Apple Business Chat
Apple requires a business of record approved via Apple Business Register, plus an integration through a Messaging Service Provider.
Setup
Get approved on Apple Business Register (a real-world process, ~weeks).
Channels → New → Apple Business Chat:
Parameters
Name | Type | Description |
|---|---|---|
|
| From Apple Business Register, under your business profile. Matched against the |
|
| CSP auth token. Required by the outbound sender — without it every reply is skipped as |
|
| Optional additional secret. |
Webhook
Apple is the other exception to the shared ingest route. The MSP relay verifies Apple's JWT and forwards a normalised envelope to a dedicated receiver:
POST /v1/webhooks/apple-bc — Public
There is one URL for the whole deployment — no publicId in it. The workspace is resolved from destinationId on the envelope. The receiver authenticates with a Bearer token checked against the deployment-wide APPLE_BC_CSP_AUTH_TOKEN environment variable, so a self-hoster has to set that before any Apple traffic is accepted.
Inbound envelopes carry text, interactive or attachment types; interactive payloads (list pickers, time pickers, Apple Pay, forms) are persisted verbatim rather than rendered as native composer controls.
LINE
The dominant chat app in Japan, Taiwan, and Thailand.
Setup
Create a LINE Official Account + a Messaging API channel in the LINE Developers Console.
Channels → New → LINE:
Parameters
Name | Type | Description |
|---|---|---|
|
| LINE Messaging API channel ID. |
|
| Verifies the webhook signature. |
|
| Use a long-lived token. Encrypted at rest. |
Webhook URL:
https://api.chatlychat.com/v1/channels/webhook/{publicId}
Viber
Common in Eastern Europe + Southeast Asia.
Setup
Create a Viber Business Account at
partners.viber.com.Channels → New → Viber:
Parameters
Name | Type | Description |
|---|---|---|
|
| From your Viber Business account dashboard. |
|
| Shown to customers in the chat header. |
|
| Optional. Shown as the bot avatar in Viber. |
Webhook:
https://api.chatlychat.com/v1/channels/webhook/{publicId}
Cross-channel behaviour
Parameters
Name | Type | Description |
|---|---|---|
|
| Each provider's own scheme, checked over the raw request bytes before anything is persisted. A bad signature is a |
|
| A webhook carrying several messages is dispatched sequentially, not in parallel — concurrent creates would race and split one thread in two. |
|
| Matched on the provider's thread key ( |
|
| Email, SMS, Telegram, Discord and Messenger are called directly by the worker. WhatsApp, Instagram, LINE, Viber, Apple, Slack, Teams and TikTok go through the API process, which owns their stateful or centralised auth. |
|
| BullMQ with exponential backoff, 8 attempts on the delivery queue. |
|
| Carried through by the URL the provider hosted them at. There is no download, no MIME sniffing and no virus scanning anywhere in the inbound path. |
Warning — Inbound is not deduplicated
Providers retry webhooks. The provider's message id is stored on
messages.metadata.externalMessageIdbut nothing checks it before inserting, and there is no unique index on it — a redelivered webhook creates a second message in the thread.
One contact across channels
Warning — Cross-channel identity is not automatic
Contact upsert matches on
externalIdonly — the provider's own id for that person. It does not match on email or phone, so the same human writing from Instagram and from email becomes two contacts. Merge them deliberately withPOST /v1/contacts/{id}/merge, which is one of the routes an API key can call.
Once merged, the inbox is conversation-first: an agent handles whatever comes in across email, WhatsApp, chat and Instagram, and replying uses that conversation's channel credentials.
Troubleshooting
Warning — A channel is configured and nothing ever arrives
Work through the transport, not the credentials. Discord needs
apps/discord-gatewayrunning. Telegram needs thesetWebhookcall to have succeeded — re-save the channel and read the registration result. Everything else needs the provider pointed at/v1/channels/webhook/{publicId}; an older hand-written path such as/v1/webhooks/{type}/{id}has never been served and 404s.
Warning — 404 on a webhook URL that looks right
The channel is disabled. The ingest route only resolves channels with
enabled = true, and answers404for a disabled one exactly as it would for a wrong id.
Info — Telegram bot doesn't see group messages
Bots get group messages in two modes: privacy mode on (only sees @mentions + replies) or privacy mode off (sees everything). Toggle via BotFather →
/setprivacy. Off is usually what you want for support bots.
Info — Why most platforms won't let you message first
WhatsApp, Messenger, Instagram and LINE all require the customer to message you first. Chatly does not add its own gate for those — the platform refuses the send. TikTok is the exception we do enforce locally: its policy allows no re-engagement of any kind, so the adapter refuses a first message outright and TikTok is excluded from the campaign channel list.