Skip to content

Channels

Updated

The chatbot you built for your website can answer on WhatsApp, Telegram, Slack, Discord, your Crisp chatbox and your support email too, with the same knowledge, tools and workflows, and every conversation lands in the same log.

Before you start, build your chatbot and make it Live under Deploy. It answers in these apps only while it is Live. Each app takes a few minutes to set up, under Channels in your dashboard and in the app itself. To put the same chatbot inside your own iPhone or iPad app, see the iOS SDK guide.

WhatsAppYour business's WhatsApp number, answering customers through Meta's Cloud API.TelegramA Telegram bot people message, in a private chat or a group.SlackAn app in your Slack workspace, in direct messages and threads.DiscordA bot in your community's Discord server, and in direct messages.EmailForward your support address, and the chatbot answers in the same thread.CrispAnswers in the Crisp chatbox you already have, and hands over to your team.

When your chatbot needs to know the user

Many chatbots only answer questions, and for those you are done. If yours also acts for a signed-in user, for example to show their projects or create one, it needs to know who is writing. On your website, the widget already carries your signed-in user's token to your tool server. In Telegram, Slack, Discord, WhatsApp and email there is no page to carry it, so each user links their account once. (Crisp is the exception: your site passes the token to the chatbox itself, as the Crisp guide shows.)

How it works for your users

  1. In a private chat with the bot, they send /connect in Telegram, or connect in a direct message in Slack or Discord. In a group, a channel or a server the bot points them to a private chat instead, so nobody else sees the link.
  2. The bot answers with a button to a page on your site.
  3. They sign in to your product as usual, and confirm that the chat is theirs.
  4. From then on, in a chat with them alone, the chatbot acts as that signed-in user, exactly as it does in the widget on your website. In a group it answers them as it answers anyone, so nothing from their account reaches the others.

They can send /disconnect or disconnect at any time to unlink. Disconnecting the app under Channels unlinks everyone who linked through it.

What to add to your product

You need one new page. Your tool server does not change: it receives the same token, in the same header, as it does from the widget.

  1. A page that requires sign-in, for example https://yourapp.com/connect-chatfrom. The button in the chat opens it with ?chatfrom_code=… added.
  2. Show who is asking. Ask ChatFrom who the code belongs to, and show it with a confirm button, such as "Connect Telegram @anna to your account?".
  3. When they confirm, send the code to ChatFrom with the same kind of token your site gives the widget for that user.
Who the code belongs toHTTP
GET https://chatfrom.io/api/channels/link/{code}
The answerJSON
{
  "chatbot": "Acme support",
  "channel": "telegram",
  "display_name": "@anna",
  "expires_at": "2026-09-21T18:15:00.000Z"
}
Link the accountHTTP
POST https://chatfrom.io/api/channels/link/{code}
{
  "user_token": "<the token you give the widget for this user>",
  "account": "anna@example.com"
}

ChatFrom answers { "linked": true }, and the user gets a message in their chat that their account is linked. account is optional: it is how your product names the account, shown back to the user. A code works once and for fifteen minutes; after that, both calls answer 404 and the user sends connect again. A 503 means ChatFrom could not check the code just then, and trying again in a moment can work.

A sketch of the page, in NodeJavaScript
// The page the "Connect your account" button opens. The user must be signed in.
app.get("/connect-chatfrom", requireLogin, async (req, res) => {
  const code = req.query.chatfrom_code;
  const who = await fetch(`https://chatfrom.io/api/channels/link/${code}`).then((r) => r.json());
  if (who.error) return res.render("link-expired");
  // Show who is asking, so the user can check it is really them.
  res.render("confirm-link", { code, app: who.channel, name: who.display_name });
});

// The user pressed "Yes, connect". Guard this POST against cross-site requests
// like every form on your site; otherwise a page that submits a code by itself
// would link without the confirmation.
app.post("/connect-chatfrom", requireLogin, csrfProtection, async (req, res) => {
  const token = signUserToken(req.user.id); // the same function that makes the widget's data-user-token
  const out = await fetch(`https://chatfrom.io/api/channels/link/${req.body.code}`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ user_token: token, account: req.user.email }),
  }).then((r) => r.json());
  res.render(out.linked ? "linked" : "link-expired");
});

Let your coding agent build it

If you work with a coding agent such as Claude Code, Cursor or Copilot, copy this prompt into it inside your product's code. It builds the page, reuses the token your widget already gets, and tells you the address to enter in ChatFrom. The same prompt is on the Channels page in your dashboard, with your chatbot's name filled in.

Prompt for your coding agent
Add a "Connect your chat account" page to our app, so users can link their Telegram, Slack, Discord or WhatsApp chat with our ChatFrom chatbot to their account here. ChatFrom already sends our signed-in users' token to our tool server from the website widget; this page lets a Telegram, Slack, Discord or WhatsApp user hand over the same token once, so the chatbot can act for them in the chat too.

How the flow works:
1. In Telegram, Slack, Discord or WhatsApp, the user sends "connect" to the bot. The bot replies with a button to our page: https://<your app>/connect-chatfrom?chatfrom_code=<code>. The code is one-time and expires after 15 minutes.
2. Our page requires the user to be signed in, using our normal login. If they are not signed in, send them to log in and bring them back to this page with the chatfrom_code kept.
3. Our server asks ChatFrom who the code belongs to:
   GET https://chatfrom.io/api/channels/link/<code>
   200: { "chatbot": "...", "channel": "telegram", "display_name": "@anna", "expires_at": "..." }
   "channel" names the chat app: "telegram", "slack", "discord", "whatsapp", "email" today, and more may come. Accept any value: show the known ones by name and anything else as "your chat app". Never treat an unfamiliar channel as an error. "display_name" is the user's name in that app and may be null when the app would not say; then show a wording without it, such as "Connect this Slack chat to your account?".
   404: { "error": "..." }  (unknown, used or expired code)
   503: { "error": "..." }  (ChatFrom could not check the code just now; offer to try again in a moment)
4. Show a confirmation, for example: "Connect Telegram @anna to your account <their email>?" with a "Yes, connect" button and a "Cancel" link. This step is required: without it, someone could send their own code to another person and link that person's account without them noticing.
5. When they confirm, our server links the account:
   POST https://chatfrom.io/api/channels/link/<code>
   Content-Type: application/json
   { "user_token": "<token for this user>", "account": "<how we name the account, e.g. their email>" }
   200: { "linked": true, "channel": "...", "display_name": "..." }
   404: the code has expired or was already used; tell the user to send "connect" again in the chat.
   400: user_token is missing or not URL-safe.
6. Show a result page: "Linked. You can go back to the chat." The user also gets a message in the chat.

Requirements:
- user_token must be the same kind of token we already give the ChatFrom widget as data-user-token for this user (reuse the function that makes it). ChatFrom never decodes it; it sends it as the X-User-Id header to our tool server (MCP), which already knows how to check it. Do not change the tool server.
- The token can be at most 1024 characters of A-Z, a-z, 0-9 and . _ ~ -.
- Choose its lifetime on purpose. If our widget tokens expire quickly, issue a longer-lived one here, because a linked chat keeps using it until the user unlinks, and the user has to link again when it expires. Our tool server should still reject it when the account is deleted or signed out everywhere.
- Call both ChatFrom endpoints from our server, not from the browser. Never put the token in a URL or log it.
- Protect the confirm POST against cross-site request forgery, as with every other form in our app.
- Treat a 404 from either call as "link expired" with a friendly page, not an error.
- Match the look of our other account pages. No new dependencies unless we already use them.

When it is done:
- Tell me the page's full address, so I can enter it in ChatFrom under Channels, "Your link page".
- Add a short test for the confirm step and for the expired-code case, in the style of our existing tests.
- Explain how to try it: send /connect to the Telegram bot (or "connect" to the Slack app, the Discord bot or the WhatsApp number in a direct message), open the button, sign in, confirm, and check the chat says the account is linked.

In ChatFrom

Under Channels, enter your page's address in Your link page and save. Until you do, the bot tells users that it does not link accounts, and it keeps answering general questions.

Two things to decide

  • Keep the confirm step, and guard it. It lets the user check that the chat is really theirs. Without it, someone could send their own code to another person and have that person link it to their account without noticing. Protect the confirm request against cross-site requests like every form on your site, or a page that submits the code by itself gets past the confirmation.
  • Choose how long the token lasts. If the tokens you give the widget expire after an hour, a linked chat stops acting for the user after an hour and they have to link again. For chat links, issue a token that lasts as long as you are happy for a linked chat to act for the user, and reject it on your server when the user signs out everywhere or deletes their account.

What your users can do

In WhatsAppIn TelegramIn SlackIn DiscordIn CrispBy emailWhat it does
Any text messageAny text messageA direct message, or a mention in a channelA direct message, or a mention or reply in a serverAny message in the chatboxAny email to the addressAsks the chatbot
A photo or a documentA photo or a document, in a private chatNot availableA file, in a direct messageA file in the chatboxNot availableThe chatbot reads it and answers about it, when visitors may attach files
new/newnewnewNot availablenewStarts a new conversation
connect/connectconnectconnectNot availableconnectLinks their account on your site, in a private chat
disconnect/disconnectdisconnectdisconnectNot availabledisconnectUnlinks it

In Crisp there are no commands: a conversation continues until a day passes without a message, as on WhatsApp, Telegram, Slack and Discord, the signed-in user comes from the chatbox itself, and email replies and conversations your team is on are left to your team in the Crisp inbox. Files follow the Visitors can attach files switch under Deploy: on, the chatbot reads a PDF, text, Markdown or CSV file, or a photo, sent in a chat with one person, the caption as the question; off, it asks for text (in Crisp it leaves the file to your team). In a group or a server channel, files are not read, so a group cannot spend your credits on pictures. Everything else works as on your website. The chatbot answers from your knowledge, uses your tools, hands off to a person, and counts towards your monthly allowance. When a workflow asks the user to pick something, the options appear as buttons. A conversation continues until a day passes without a message, or two weeks by email, and every conversation appears under Conversations, marked with the app it came from.

The first answer of each conversation starts with “You're chatting with an AI assistant”, in every app. The EU AI Act says people must be told when they are talking to an AI, unless it is already obvious, so this is on unless you turn off Tell visitors it's an AI assistant under Deploy.

Privacy

Your Meta app and WhatsApp number, the Telegram bot, the Slack app, the Discord bot and your Crisp workspace belong to you, in your own account with each service. Messages pass through that service on your behalf, so name them in your own privacy policy, next to ChatFrom. ChatFrom stores their tokens and secrets, and the tokens of linked accounts, encrypted. What ChatFrom keeps is described in our privacy policy and our data processing agreement.