# Enterprise Channels

Enterprise collaboration platform integrations.

---

## Microsoft Teams (sidecar)

Teams migrated from the in-process Rust adapter to a Python sidecar (`librefang.sidecar.adapters.teams`, stdlib-only). The in-process `[channels.teams]` config block is no longer recognised.

### Prerequisites

- A Microsoft Azure account with access to the [Azure Bot Service](https://learn.microsoft.com/en-us/microsoftteams/platform/)
- A registered Azure AD application with a client ID and secret
- `python3` on the daemon host (no third-party packages required)

### Setup

1. Go to the [Azure Portal](https://portal.azure.com/) and create a new **Bot Channels Registration** (or Azure Bot resource).
2. Under **Configuration**, note the **Microsoft App ID**.
3. Go to **Certificates & secrets** and create a new client secret. Copy it immediately.
4. In the Teams Admin Center, enable your bot for the organization or upload a custom app manifest.
5. In the Teams portal **Outgoing Webhook** settings (or your bot's Channels config), grab the **Security Token** (base64) — it gates HMAC-SHA256 verification of every inbound activity.
6. Add to `config.toml`:

```toml
[[sidecar_channels]]
name = "teams"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.teams"]
channel_type = "teams"
[sidecar_channels.env]
TEAMS_APP_ID = "00000000-0000-0000-0000-000000000000"
TEAMS_WEBHOOK_PORT = "8459"
# TEAMS_ALLOWED_TENANTS = "tenant-a,tenant-b"   # empty = all
```

7. Put `TEAMS_APP_PASSWORD` (the client secret) + `TEAMS_SECURITY_TOKEN` in `~/.librefang/secrets.env` (the dashboard's Channels page writes them there).
8. In Azure Bot Channel configuration, set the messaging endpoint to your LibreFang sidecar URL — e.g. `https://your-domain.com:8459/webhook`.
9. Restart the daemon.

### How It Works

The sidecar runs its own webhook server (stdlib `BaseHTTPRequestHandler`), verifies inbound `Authorization: HMAC <…>` signatures against the base64-decoded `TEAMS_SECURITY_TOKEN`, and parses the Bot Framework activity JSON. Outbound replies acquire a bearer token via the OAuth2 client-credentials flow (cached with a 5-minute refresh buffer) and POST to `{serviceUrl}/v3/conversations/{id}/activities`. The per-conversation `serviceUrl` from the most-recent inbound is cached so replies hit the correct Microsoft region (an improvement over the Rust adapter, which always used the default `smba.trafficmanager.net` endpoint).

---

## Mattermost (sidecar)

Mattermost migrated from an in-process Rust adapter to an out-of-process Python sidecar (`librefang.sidecar.adapters.mattermost`).

### Prerequisites

- A Mattermost server (self-hosted or cloud) with admin access
- A bot account or personal access token
- Python 3 with the `librefang` SDK installed (`pip install -e sdk/python`)

### Setup

1. In Mattermost, go to **Integrations > Bot Accounts** and create a new bot, or go to **Account Settings > Security > Personal Access Tokens** and generate a token.
2. Copy the token and add it to `~/.librefang/secrets.env`:

```bash
MATTERMOST_TOKEN=your-bot-token
```

3. Declare the sidecar in `~/.librefang/config.toml`:

```toml
[[sidecar_channels]]
name = "mattermost"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.mattermost"]
channel_type = "mattermost"
default_agent = "ops"
[sidecar_channels.env]
MATTERMOST_SERVER_URL = "https://mattermost.example.com"
# MATTERMOST_ALLOWED_CHANNELS = "ch-id-1,ch-id-2"    # optional, empty = all
# MATTERMOST_ACCOUNT_ID = "team-prod"                # optional, multi-bot routing
```

4. Restart the daemon.

### How It Works

The sidecar connects to the Mattermost server over WebSocket (`wss://<host>/api/v4/websocket`) and sends an `authentication_challenge` immediately after the upgrade handshake. It subscribes to `posted` events, deduplicates on `post.id`, and emits messages to the kernel over its stdio channel. Outbound messages use `POST /api/v4/posts` with the inbound `root_id` round-tripped so threaded replies stay in their thread.

---

## Google Chat

### Prerequisites

- A Google Cloud project with the [Google Chat API](https://developers.google.com/workspace/chat/) enabled
- A service account key file (JSON)

### Setup

1. In the [Google Cloud Console](https://console.cloud.google.com/), enable the Google Chat API.
2. Create a service account and download the JSON key file.
3. Configure a Chat app in the Google Chat API settings, selecting the **HTTP endpoint** connection type. Set the endpoint URL to your LibreFang instance (e.g., `https://your-domain.com:4545/channels/google_chat/webhook`).
4. Set environment variables:

```bash
export GOOGLE_CHAT_SA_KEY=/path/to/sa-key.json  # Environment variable
export GOOGLE_CHAT_SPACE=spaces/AAAA_BBBBBB
librefang vault set GOOGLE_CHAT_SA_KEY        # Encrypted vault (recommended)
librefang vault set GOOGLE_CHAT_SPACE
librefang config set-key google_chat          # .env file
# Or set via dashboard "Set API Key" button   # secrets.env
```

5. Add to config (Google Chat runs as an out-of-process sidecar adapter, `librefang.sidecar.adapters.google_chat`):

```toml
[[sidecar_channels]]
name = "google_chat"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.google_chat"]
channel_type = "google_chat"
[sidecar_channels.env]
GOOGLE_CHAT_WEBHOOK_PORT = "8090"
# GOOGLE_CHAT_SPACE_IDS = "spaces/AAAA,spaces/BBBB"   # optional, empty = all spaces
# GOOGLE_CHAT_ACCOUNT_ID = "ops"                      # optional, multi-bot routing
```

`GOOGLE_CHAT_SERVICE_ACCOUNT_JSON` (the full JSON blob — not a path) belongs in `~/.librefang/secrets.env`.

6. Hot-reload (`curl -X POST http://127.0.0.1:4545/api/channels/reload`) or restart the daemon.

### How It Works

The sidecar authenticates with the service account JSON and runs its own HTTP webhook server on `GOOGLE_CHAT_WEBHOOK_PORT` (default 8090). The Google Cloud Console Bot configuration messaging endpoint should point at `https://<host>:<port>/webhook`. Responses go back through the Google Chat REST API using the space and thread identifiers from each inbound message.

---

## Webex (sidecar)

### Prerequisites

- A Webex bot token (from the [Webex Developer Portal](https://developer.webex.com/docs/api/getting-started))

### Setup

Webex migrated to an out-of-process sidecar in v2026.5 (replaces the former in-process Rust adapter). The bot Bearer token belongs in `~/.librefang/secrets.env`, not `config.toml`.

1. Go to [developer.webex.com](https://developer.webex.com/) and sign in.
2. Navigate to **My Webex Apps** and create a new Bot.
3. Copy the bot access token.
4. Save the token in `~/.librefang/secrets.env` (the dashboard's Channels page does this for you):

```bash
WEBEX_BOT_TOKEN=your-webex-bot-token
```

5. Declare the sidecar in `config.toml`:

```bash
[[sidecar_channels]]
name = "webex"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.webex"]
channel_type = "webex"
default_agent = "assistant"

[sidecar_channels.env]
# WEBEX_ALLOWED_ROOMS = "Y2lz...A,Y2lz...B"  # optional, empty = all rooms
# WEBEX_ACCOUNT_ID = "org-prod"              # optional, multi-bot routing
```

6. Restart the daemon.

### How It Works

The Webex sidecar (`librefang.sidecar.adapters.webex`) connects to Cisco's Mercury WebSocket gateway for real-time message activity, then fetches the full message body via the Webex REST API (`GET /messages/<id>`). Outbound replies are posted via `POST /messages`, optionally with `parentId` set to thread under the originating message (improvement over the Rust adapter, which never sent `parentId`). 429 responses on either side honour the `Retry-After` header and retry once.

---

## Feishu / Lark

Feishu / Lark is provided by the Python sidecar adapter (`librefang.sidecar.adapters.feishu`). The in-process `[channels.feishu]` config block was removed in the sidecar migration. Declare Feishu/Lark as a `[[sidecar_channels]]` entry instead.

### Prerequisites

- A Feishu (or Lark) custom app with Bot capabilities (from the [Feishu Open Platform](https://open.feishu.cn/document/))
- `python3` on the daemon host (no third-party Python packages required — the sidecar is stdlib-only)

### Setup

1. Go to the [Feishu Open Platform](https://open.feishu.cn/) and create a custom application.
2. Enable **Bot** under the app's capabilities.
3. Note the **App ID** and **App Secret** from the app credentials page.
4. Add to `config.toml`:

```toml
[[sidecar_channels]]
name = "feishu"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.feishu"]
channel_type = "feishu"
[sidecar_channels.env]
FEISHU_APP_ID = "cli_abc123"
# FEISHU_REGION = "cn"                       # cn (default) or intl (Lark)
# FEISHU_RECEIVE_MODE = "websocket"          # websocket (default) or webhook
# FEISHU_WEBHOOK_PORT = "8453"               # webhook mode only
# FEISHU_VERIFICATION_TOKEN = "..."          # webhook mode only
# FEISHU_ENCRYPT_KEY = "..."                 # webhook mode only — AES-256-CBC payloads
# FEISHU_ACCOUNT_ID = "prod"                 # optional, multi-bot routing key
```

5. Put `FEISHU_APP_SECRET` in `~/.librefang/secrets.env` (the dashboard's Channels page writes it for you).
6. Restart the daemon.

### How It Works

The sidecar defaults to `websocket` mode: it discovers the real `wss://` URL via `POST /callback/ws/endpoint` and opens a long-lived gateway connection (no public IP required). `webhook` mode listens on `FEISHU_WEBHOOK_PORT` and accepts both plaintext and AES-256-CBC + PKCS7 encrypted payloads (with `FEISHU_ENCRYPT_KEY` configured). Outbound replies go through `POST /open-apis/im/v1/messages` with a tenant access token cached and refreshed automatically (2-hour expiry with a 5-minute refresh buffer).

<Note type="warning">
**Migrating from the in-process adapter (webhook mode)**: the webhook URL changed. The in-process adapter served webhooks at `<api-host>:<api-port>/channels/feishu/webhook` (the shared API port). The sidecar runs its own HTTP server on `FEISHU_WEBHOOK_PORT` (default `8453`) and serves at `<sidecar-host>:8453/webhook`. **Update your Feishu app's event-subscription URL** in the Open Platform console before the sidecar takes over.

Request bodies larger than 1 MiB are rejected with `413 Payload Too Large` — Feishu events are well under 64 KB in practice, the cap exists to prevent a misbehaving (or malicious) sender from OOMing the sidecar.
</Note>

### Processing-State Reactions

Feishu shows live "I'm working on it" feedback by adding a `Typing` reaction to the user's message on receive and removing it once the reply is sent. Both calls are fire-and-forget — API failures log a warning but never block message processing. See [Reactions and Processing State](/integrations/channels#reactions-and-processing-state) on the channels overview.

### @Mention Preservation

The sidecar substitutes each `@_user_N` placeholder with `@<display-name>` resolved from the mention payload (falling back to `open_id` when name is absent), and rewrites `@_all` to `@all`. Agents see the original conversational text — "@alice can you check this?" — instead of bare punctuation. See [Feishu @Mention Preservation](/integrations/channels#feishu-mention-preservation).

---

## Rocket.Chat

Rocket.Chat is provided by the Python sidecar adapter (`librefang.sidecar.adapters.rocketchat`). The in-process `[channels.rocketchat]` config block was removed in the sidecar migration. Declare Rocket.Chat as a `[[sidecar_channels]]` entry instead.

### Prerequisites

- A Rocket.Chat server with admin access
- A bot user account with a personal access token
- Python 3.8+ on the daemon host (the sidecar runs as `python3 -m librefang.sidecar.adapters.rocketchat`)

### Setup

1. In Rocket.Chat, go to **Administration > Users** and create a bot user (or pick an existing service account).
2. Sign in as that bot and generate a personal access token via **My Account > Personal Access Tokens**. Note both the token and the bot's user ID.
3. Add the sidecar entry to your `config.toml`:

```bash
[[sidecar_channels]]
name = "rocketchat"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.rocketchat"]
channel_type = "rocketchat"
[sidecar_channels.env]
ROCKETCHAT_SERVER_URL = "https://chat.example.com"
ROCKETCHAT_USER_ID = "abc123"
# ROCKETCHAT_CHANNELS = "GENERAL,room2"        # optional; empty = all joined
# ROCKETCHAT_ACCOUNT_ID = "prod"                # optional, multi-bot routing key
# ROCKETCHAT_POLL_INTERVAL_SECS = "2"           # optional, default 2, floor 1
```

4. Put `ROCKETCHAT_TOKEN` (the personal access token) in `~/.librefang/secrets.env` — the dashboard's Channels page writes it there when you fill the Rocket.Chat form.
5. Restart the daemon.

### How It Works

The sidecar polls `GET /api/v1/channels.history` per room at the configured interval (default 2 s) using `X-Auth-Token` / `X-User-Id` headers. Slash-command bodies (`/cmd args`) become `Command` events; everything else is plain text. Outbound replies go through `POST /api/v1/chat.postMessage` and thread correctly via `tmid` when the inbound message lived inside a thread (improvement over the former Rust adapter, which always replied at the room root).

---

## Zulip

Zulip is provided by the out-of-process Python sidecar adapter `librefang.sidecar.adapters.zulip`. The in-process `[channels.zulip]` block was removed in the sidecar migration.

### Prerequisites

- A Zulip server account (cloud or self-hosted)
- A bot created in **Settings > Your bots** (see [Zulip API docs](https://zulip.com/api/))

### Setup

1. In Zulip, go to **Settings > Your bots** and click **Add a new bot**.
2. Choose **Generic bot** and create it.
3. Note the bot's email address and API key.
4. Declare the sidecar in your config:

```bash
[[sidecar_channels]]
name = "zulip"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.zulip"]
channel_type = "zulip"
[sidecar_channels.env]
ZULIP_SERVER_URL = "https://myorg.zulipchat.com"
ZULIP_BOT_EMAIL  = "bot-bot@myorg.zulipchat.com"
# ZULIP_STREAMS = "engineering, general"      # optional; empty = all subscribed
# ZULIP_ACCOUNT_ID = "prod"                   # optional, multi-bot routing key
```

5. Put the bot's API key in `~/.librefang/secrets.env`:

```bash
ZULIP_API_KEY=your-api-key
```

6. Restart the daemon.

### How It Works

The sidecar uses Zulip's event queue API for long-polling. At startup it calls `GET /api/v1/users/me` to discover the bot's stable user id, registers an event queue via `POST /api/v1/register` (narrowed to the configured `ZULIP_STREAMS` if set), then long-polls `GET /api/v1/events` until shutdown. Inbound messages are filtered by the bot's own user id (so renames don't break self-skip) and deduped by `message.id` (so a `BAD_EVENT_QUEUE_ID` re-register can't re-emit). Outbound responses go through `POST /api/v1/messages`, using `cmd.thread_id` as the target topic so replies land in the originating topic. 429 responses on any of the four REST paths honour `Retry-After`.
