> ## Documentation Index
> Fetch the complete documentation index at: https://docs.plungeai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bot channels, voice and the chat widget

> Talk to a bot from Telegram, WhatsApp, Discord, Teams or Slack. Pairing and bindings, the one-message mention, voice notes, and the embeddable web chat widget.


A bot can answer in the chats you already use. Every run started from a chat is a normal run of the bot, as its owner: it lands in your run history and stops at the bot's stored policy and spend caps.

## Pair a chat, bind it to a bot

<Note>
Bind and unbind answer 503 `not_enabled` until the platform owner enables them, and the bindings list is empty until then; the API documents the code.
</Note>

The channels are `telegram`, `whatsapp`, `discord` and `teams`; Slack is bound through the Slack app. Two steps:

1. **Pair the chat.** `POST /v1/channels/pair` with `{"channel": "telegram"}` returns a `code` valid for 10 minutes. Send it to the channel's bot from the chat (`/pair <code>`). The chat then appears in `GET /v1/channels` with its `sender_id`.
2. **Bind it to a bot.** `POST /v1/workflows/{id}/bindings` with `{"channel": "telegram", "sender_id": "<sender id>"}`.

```bash
curl -s -X POST https://api.plungeai.com/v1/channels/pair \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
  -d '{"channel":"telegram"}'
```

- A chat talks to **one** bot: binding it again moves it.
- A chat you did not pair is `422 not_paired`. An account at its binding limit is `409 binding_limit`.
- `GET /v1/workflows/{id}/bindings` lists the chats bound to a bot; `DELETE /v1/workflows/{id}/bindings/{channel}/{sender_id}` unbinds one.
- Studio has the same controls under the bot's channel bindings. The CLI has `workflows bindings list|bind|unbind`.

You can also bind from inside the chat: `/bot <name>` talks to one of your bots there, and `/unbind` releases it. On Slack the commands are `/plunge bind <name>` and `/plunge unbind`. If a bot and a flow share a name, the bot wins.

### Address one bot for one message

Start a message with `@<botname>` to run one bot on that message without changing the binding: `@research summarize this` runs the bot named `research` on `summarize this`, and the next plain message routes normally again.

- The name must match one of your bots exactly, ignoring case. The `@` must be the first character.
- Two bots with the same name are refused with a message, not guessed; no match answers `No bot named "<name>" in your account.`
- Works on Telegram, WhatsApp, Discord and Slack.

## Voice notes

<Note>
Voice notes work once the platform owner has deployed the updated message gateway, the same deploy as channel bindings.
</Note>

A voice note sent to a paired Telegram or WhatsApp chat is transcribed with Whisper, and the transcript is handled like typed text.

- The audio is capped at 25 MB. A failed transcription answers with a fixed message asking you to type it or send it again.
- The reply to a voice note comes back as **audio**: the first 4,000 characters are spoken (`tts-1`, voice `nova`, mp3). When the reply is longer, or when speech or the send fails, the text is sent too or instead, so a voice note is never left unanswered.
- Typed messages are always answered in text.
- **Discord and Teams are text-only.**
- The paired owner's own OpenAI key is tried before the platform key.

## Microsoft Teams

Teams has two parts.

**Result delivery.** `parameters.deliver` takes `{"channel": "teams", "chat_id": "<Graph chat id>"}`, and the post is made as you, with your own Microsoft connection. With no Microsoft connection the delivery fails with `owner has no microsoft connection`; pairing is for inbound chats and is not what delivery needs. A channel target (`<teamId>/<channelId>`) is accepted by the format check but refused at delivery until the `ChannelMessage.Send` permission is granted.

**The two-way chat bot.** Through the Bot Framework, the gateway verifies each inbound activity and replies to the conversation it came from. It is text only.

<Note>
The two-way Teams bot is off until the platform owner sets up its Azure bot registration, and `teams` cannot be paired before then. Result delivery does not need it.
</Note>

## Channel security

The gateway refuses a channel request it cannot verify. A missing secret, a missing or wrong signature header and any error inside the check all answer `401`; only a POST is processed. Telegram is verified by its secret-token header, WhatsApp by an `X-Hub-Signature-256` HMAC and Discord by an Ed25519 signature.

## Web chat widget

A script tag puts a chat bubble for one bot on your own site.

```html
<script src="https://studio.plungeai.com/embed.js" data-bot="<widget token>" async></script>
```

<Note>
Creating or changing a widget answers 503 `widgets not enabled yet` until the platform owner enables it; the API documents the code. `embed.js` itself is always served.
</Note>

Create the widget in Studio, from the bot's menu, with these settings:

| Setting | Meaning |
|---|---|
| Allowed origins | The sites that may frame the chat and call it. Only these origins are answered with CORS headers; never `*`. |
| Enabled | Off answers `403`. |
| Rate per minute | Messages per minute per widget, 1 to 600. Default 20. |
| Greeting | The first line the visitor sees, up to 300 characters. |

- The token is shown once, when the widget is created or rotated. Rotating stops the old snippet.
- Each visitor message is one run of the bot as its owner. The text is only ever the run's `input`, and the run's memory is the bot's own, not yours.
- The visitors spend your budget. Set a per-run spend cap on the bot first ([Permissions and budget](/bots/permissions-and-budget)); Studio warns when the bot has none. The per-run cap is the cost fence; the monthly cap only gates scheduled runs.
- Rate limits apply per address and per widget, a message is at most 4,000 characters, and a conversation id (valid 24 hours) keeps a visitor's thread together.
- The chat shell renders every reply as plain text, so a bot cannot inject markup into the page.
