diff --git a/docs/channels/mattermost.md b/docs/channels/mattermost.md index c2c35814f11..72cbc0469cc 100644 --- a/docs/channels/mattermost.md +++ b/docs/channels/mattermost.md @@ -4,62 +4,68 @@ read_when: - Setting up Mattermost - Debugging Mattermost routing title: "Mattermost" +sidebarTitle: "Mattermost" --- -Status: bundled plugin (bot token + WebSocket events). Channels, groups, and DMs are supported. -Mattermost is a self-hostable team messaging platform; see the official site at -[mattermost.com](https://mattermost.com) for product details and downloads. +Status: bundled plugin (bot token + WebSocket events). Channels, groups, and DMs are supported. Mattermost is a self-hostable team messaging platform; see the official site at [mattermost.com](https://mattermost.com) for product details and downloads. ## Bundled plugin -Mattermost ships as a bundled plugin in current OpenClaw releases, so normal -packaged builds do not need a separate install. + +Mattermost ships as a bundled plugin in current OpenClaw releases, so normal packaged builds do not need a separate install. + -If you are on an older build or a custom install that excludes Mattermost, -install it manually: +If you are on an older build or a custom install that excludes Mattermost, install it manually: -Install via CLI (npm registry): - -```bash -openclaw plugins install @openclaw/mattermost -``` - -Local checkout (when running from a git repo): - -```bash -openclaw plugins install ./path/to/local/mattermost-plugin -``` + + + ```bash + openclaw plugins install @openclaw/mattermost + ``` + + + ```bash + openclaw plugins install ./path/to/local/mattermost-plugin + ``` + + Details: [Plugins](/tools/plugin) ## Quick setup -1. Ensure the Mattermost plugin is available. - - Current packaged OpenClaw releases already bundle it. - - Older/custom installs can add it manually with the commands above. -2. Create a Mattermost bot account and copy the **bot token**. -3. Copy the Mattermost **base URL** (e.g., `https://chat.example.com`). -4. Configure OpenClaw and start the gateway. + + + Current packaged OpenClaw releases already bundle it. Older/custom installs can add it manually with the commands above. + + + Create a Mattermost bot account and copy the **bot token**. + + + Copy the Mattermost **base URL** (e.g., `https://chat.example.com`). + + + Minimal config: -Minimal config: + ```json5 + { + channels: { + mattermost: { + enabled: true, + botToken: "mm-token", + baseUrl: "https://chat.example.com", + dmPolicy: "pairing", + }, + }, + } + ``` -```json5 -{ - channels: { - mattermost: { - enabled: true, - botToken: "mm-token", - baseUrl: "https://chat.example.com", - dmPolicy: "pairing", - }, - }, -} -``` + + ## Native slash commands -Native slash commands are opt-in. When enabled, OpenClaw registers `oc_*` slash commands via -the Mattermost API and receives callback POSTs on the gateway HTTP server. +Native slash commands are opt-in. When enabled, OpenClaw registers `oc_*` slash commands via the Mattermost API and receives callback POSTs on the gateway HTTP server. ```json5 { @@ -77,27 +83,33 @@ the Mattermost API and receives callback POSTs on the gateway HTTP server. } ``` -Notes: + + + - `native: "auto"` defaults to disabled for Mattermost. Set `native: true` to enable. + - If `callbackUrl` is omitted, OpenClaw derives one from gateway host/port + `callbackPath`. + - For multi-account setups, `commands` can be set at the top level or under `channels.mattermost.accounts..commands` (account values override top-level fields). + - Command callbacks are validated with the per-command tokens returned by Mattermost when OpenClaw registers `oc_*` commands. + - Slash callbacks fail closed when registration failed, startup was partial, or the callback token does not match one of the registered commands. + + + The callback endpoint must be reachable from the Mattermost server. + + - Do not set `callbackUrl` to `localhost` unless Mattermost runs on the same host/network namespace as OpenClaw. + - Do not set `callbackUrl` to your Mattermost base URL unless that URL reverse-proxies `/api/channels/mattermost/command` to OpenClaw. + - A quick check is `curl https:///api/channels/mattermost/command`; a GET should return `405 Method Not Allowed` from OpenClaw, not `404`. + + + + If your callback targets private/tailnet/internal addresses, set Mattermost `ServiceSettings.AllowedUntrustedInternalConnections` to include the callback host/domain. + + Use host/domain entries, not full URLs. -- `native: "auto"` defaults to disabled for Mattermost. Set `native: true` to enable. -- If `callbackUrl` is omitted, OpenClaw derives one from gateway host/port + `callbackPath`. -- For multi-account setups, `commands` can be set at the top level or under - `channels.mattermost.accounts..commands` (account values override top-level fields). -- Command callbacks are validated with the per-command tokens returned by - Mattermost when OpenClaw registers `oc_*` commands. -- Slash callbacks fail closed when registration failed, startup was partial, or - the callback token does not match one of the registered commands. -- Reachability requirement: the callback endpoint must be reachable from the Mattermost server. - - Do not set `callbackUrl` to `localhost` unless Mattermost runs on the same host/network namespace as OpenClaw. - - Do not set `callbackUrl` to your Mattermost base URL unless that URL reverse-proxies `/api/channels/mattermost/command` to OpenClaw. - - A quick check is `curl https:///api/channels/mattermost/command`; a GET should return `405 Method Not Allowed` from OpenClaw, not `404`. -- Mattermost egress allowlist requirement: - - If your callback targets private/tailnet/internal addresses, set Mattermost - `ServiceSettings.AllowedUntrustedInternalConnections` to include the callback host/domain. - - Use host/domain entries, not full URLs. - Good: `gateway.tailnet-name.ts.net` - Bad: `https://gateway.tailnet-name.ts.net` + + + ## Environment variables (default account) Set these on the gateway host if you prefer env vars: @@ -105,17 +117,27 @@ Set these on the gateway host if you prefer env vars: - `MATTERMOST_BOT_TOKEN=...` - `MATTERMOST_URL=https://chat.example.com` + Env vars apply only to the **default** account (`default`). Other accounts must use config values. `MATTERMOST_URL` cannot be set from a workspace `.env`; see [Workspace `.env` files](/gateway/security). + ## Chat modes Mattermost responds to DMs automatically. Channel behavior is controlled by `chatmode`: -- `oncall` (default): respond only when @mentioned in channels. -- `onmessage`: respond to every channel message. -- `onchar`: respond when a message starts with a trigger prefix. + + + Respond only when @mentioned in channels. + + + Respond to every channel message. + + + Respond when a message starts with a trigger prefix. + + Config example: @@ -137,12 +159,10 @@ Notes: ## Threading and sessions -Use `channels.mattermost.replyToMode` to control whether channel and group replies stay in the -main channel or start a thread under the triggering post. +Use `channels.mattermost.replyToMode` to control whether channel and group replies stay in the main channel or start a thread under the triggering post. - `off` (default): only reply in a thread when the inbound post is already in one. -- `first`: for top-level channel/group posts, start a thread under that post and route the - conversation to a thread-scoped session. +- `first`: for top-level channel/group posts, start a thread under that post and route the conversation to a thread-scoped session. - `all`: same behavior as `first` for Mattermost today. - Direct messages ignore this setting and stay non-threaded. @@ -161,8 +181,7 @@ Config example: Notes: - Thread-scoped sessions use the triggering post id as the thread root. -- `first` and `all` are currently equivalent because once Mattermost has a thread root, - follow-up chunks and media continue in that same thread. +- `first` and `all` are currently equivalent because once Mattermost has a thread root, follow-up chunks and media continue in that same thread. ## Access control (DMs) @@ -176,8 +195,7 @@ Notes: - Default: `channels.mattermost.groupPolicy = "allowlist"` (mention-gated). - Allowlist senders with `channels.mattermost.groupAllowFrom` (user IDs recommended). -- Per-channel mention overrides live under `channels.mattermost.groups..requireMention` - or `channels.mattermost.groups["*"].requireMention` for a default. +- Per-channel mention overrides live under `channels.mattermost.groups..requireMention` or `channels.mattermost.groups["*"].requireMention` for a default. - `@username` matching is mutable and only enabled when `channels.mattermost.dangerouslyAllowNameMatching: true`. - Open channels: `channels.mattermost.groupPolicy="open"` (mention-gated). - Runtime note: if `channels.mattermost` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set). @@ -206,6 +224,7 @@ Use these target formats with `openclaw message send` or cron/webhooks: - `user:` for a DM - `@username` for a DM (resolved via the Mattermost API) + Bare opaque IDs (like `64ifufp...`) are **ambiguous** in Mattermost (user ID vs channel ID). OpenClaw resolves them **user-first**: @@ -214,14 +233,13 @@ OpenClaw resolves them **user-first**: - Otherwise the ID is treated as a **channel ID**. If you need deterministic behavior, always use the explicit prefixes (`user:` / `channel:`). + ## DM channel retry -When OpenClaw sends to a Mattermost DM target and needs to resolve the direct channel first, it -retries transient direct-channel creation failures by default. +When OpenClaw sends to a Mattermost DM target and needs to resolve the direct channel first, it retries transient direct-channel creation failures by default. -Use `channels.mattermost.dmChannelRetry` to tune that behavior globally for the Mattermost plugin, -or `channels.mattermost.accounts..dmChannelRetry` for one account. +Use `channels.mattermost.dmChannelRetry` to tune that behavior globally for the Mattermost plugin, or `channels.mattermost.accounts..dmChannelRetry` for one account. ```json5 { @@ -260,15 +278,19 @@ Enable via `channels.mattermost.streaming`: } ``` -Notes: - -- `partial` is the usual choice: one preview post that is edited as the reply grows, then finalized with the complete answer. -- `block` uses append-style draft chunks inside the preview post. -- `progress` shows a status preview while generating and only posts the final answer at completion. -- `off` disables preview streaming. -- If the stream cannot be finalized in place (for example the post was deleted mid-stream), OpenClaw falls back to sending a fresh final post so the reply is never lost. -- Reasoning-only payloads are suppressed from channel posts, including text that arrives as a `> Reasoning:` blockquote. Set `/reasoning on` to see thinking in other surfaces; the Mattermost final post keeps the answer only. -- See [Streaming](/concepts/streaming#preview-streaming-modes) for the channel-mapping matrix. + + + - `partial` is the usual choice: one preview post that is edited as the reply grows, then finalized with the complete answer. + - `block` uses append-style draft chunks inside the preview post. + - `progress` shows a status preview while generating and only posts the final answer at completion. + - `off` disables preview streaming. + + + - If the stream cannot be finalized in place (for example the post was deleted mid-stream), OpenClaw falls back to sending a fresh final post so the reply is never lost. + - Reasoning-only payloads are suppressed from channel posts, including text that arrives as a `> Reasoning:` blockquote. Set `/reasoning on` to see thinking in other surfaces; the Mattermost final post keeps the answer only. + - See [Streaming](/concepts/streaming#preview-streaming-modes) for the channel-mapping matrix. + + ## Reactions (message tool) @@ -292,8 +314,7 @@ Config: ## Interactive buttons (message tool) -Send messages with clickable buttons. When a user clicks a button, the agent receives the -selection and can respond. +Send messages with clickable buttons. When a user clicks a button, the agent receives the selection and can respond. Enable buttons by adding `inlineButtons` to the channel capabilities: @@ -315,44 +336,46 @@ message action=send channel=mattermost target=channel: buttons=[[{"te Button fields: -- `text` (required): display label. -- `callback_data` (required): value sent back on click (used as the action ID). -- `style` (optional): `"default"`, `"primary"`, or `"danger"`. + + Display label. + + + Value sent back on click (used as the action ID). + + + Button style. + When a user clicks a button: -1. All buttons are replaced with a confirmation line (e.g., "✓ **Yes** selected by @user"). -2. The agent receives the selection as an inbound message and responds. + + + All buttons are replaced with a confirmation line (e.g., "✓ **Yes** selected by @user"). + + + The agent receives the selection as an inbound message and responds. + + -Notes: - -- Button callbacks use HMAC-SHA256 verification (automatic, no config needed). -- Mattermost strips callback data from its API responses (security feature), so all buttons - are removed on click — partial removal is not possible. -- Action IDs containing hyphens or underscores are sanitized automatically - (Mattermost routing limitation). - -Config: - -- `channels.mattermost.capabilities`: array of capability strings. Add `"inlineButtons"` to - enable the buttons tool description in the agent system prompt. -- `channels.mattermost.interactions.callbackBaseUrl`: optional external base URL for button - callbacks (for example `https://gateway.example.com`). Use this when Mattermost cannot - reach the gateway at its bind host directly. -- In multi-account setups, you can also set the same field under - `channels.mattermost.accounts..interactions.callbackBaseUrl`. -- If `interactions.callbackBaseUrl` is omitted, OpenClaw derives the callback URL from - `gateway.customBindHost` + `gateway.port`, then falls back to `http://localhost:`. -- Reachability rule: the button callback URL must be reachable from the Mattermost server. - `localhost` only works when Mattermost and OpenClaw run on the same host/network namespace. -- If your callback target is private/tailnet/internal, add its host/domain to Mattermost - `ServiceSettings.AllowedUntrustedInternalConnections`. + + + - Button callbacks use HMAC-SHA256 verification (automatic, no config needed). + - Mattermost strips callback data from its API responses (security feature), so all buttons are removed on click — partial removal is not possible. + - Action IDs containing hyphens or underscores are sanitized automatically (Mattermost routing limitation). + + + - `channels.mattermost.capabilities`: array of capability strings. Add `"inlineButtons"` to enable the buttons tool description in the agent system prompt. + - `channels.mattermost.interactions.callbackBaseUrl`: optional external base URL for button callbacks (for example `https://gateway.example.com`). Use this when Mattermost cannot reach the gateway at its bind host directly. + - In multi-account setups, you can also set the same field under `channels.mattermost.accounts..interactions.callbackBaseUrl`. + - If `interactions.callbackBaseUrl` is omitted, OpenClaw derives the callback URL from `gateway.customBindHost` + `gateway.port`, then falls back to `http://localhost:`. + - Reachability rule: the button callback URL must be reachable from the Mattermost server. `localhost` only works when Mattermost and OpenClaw run on the same host/network namespace. + - If your callback target is private/tailnet/internal, add its host/domain to Mattermost `ServiceSettings.AllowedUntrustedInternalConnections`. + + ### Direct API integration (external scripts) -External scripts and webhooks can post buttons directly via the Mattermost REST API -instead of going through the agent's `message` tool. Use `buildButtonAttachments()` from -the plugin when possible; if posting raw JSON, follow these rules: +External scripts and webhooks can post buttons directly via the Mattermost REST API instead of going through the agent's `message` tool. Use `buildButtonAttachments()` from the plugin when possible; if posting raw JSON, follow these rules: **Payload structure:** @@ -386,29 +409,38 @@ the plugin when possible; if posting raw JSON, follow these rules: } ``` -**Critical rules:** + +**Critical rules** 1. Attachments go in `props.attachments`, not top-level `attachments` (silently ignored). 2. Every action needs `type: "button"` — without it, clicks are swallowed silently. 3. Every action needs an `id` field — Mattermost ignores actions without IDs. -4. Action `id` must be **alphanumeric only** (`[a-zA-Z0-9]`). Hyphens and underscores break - Mattermost's server-side action routing (returns 404). Strip them before use. -5. `context.action_id` must match the button's `id` so the confirmation message shows the - button name (e.g., "Approve") instead of a raw ID. +4. Action `id` must be **alphanumeric only** (`[a-zA-Z0-9]`). Hyphens and underscores break Mattermost's server-side action routing (returns 404). Strip them before use. +5. `context.action_id` must match the button's `id` so the confirmation message shows the button name (e.g., "Approve") instead of a raw ID. 6. `context.action_id` is required — the interaction handler returns 400 without it. + -**HMAC token generation:** +**HMAC token generation** -The gateway verifies button clicks with HMAC-SHA256. External scripts must generate tokens -that match the gateway's verification logic: +The gateway verifies button clicks with HMAC-SHA256. External scripts must generate tokens that match the gateway's verification logic: -1. Derive the secret from the bot token: - `HMAC-SHA256(key="openclaw-mattermost-interactions", data=botToken)` -2. Build the context object with all fields **except** `_token`. -3. Serialize with **sorted keys** and **no spaces** (the gateway uses `JSON.stringify` - with sorted keys, which produces compact output). -4. Sign: `HMAC-SHA256(key=secret, data=serializedContext)` -5. Add the resulting hex digest as `_token` in the context. + + + `HMAC-SHA256(key="openclaw-mattermost-interactions", data=botToken)` + + + Build the context object with all fields **except** `_token`. + + + Serialize with **sorted keys** and **no spaces** (the gateway uses `JSON.stringify` with sorted keys, which produces compact output). + + + `HMAC-SHA256(key=secret, data=serializedContext)` + + + Add the resulting hex digest as `_token` in the context. + + Python example: @@ -427,22 +459,18 @@ token = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest() context = {**ctx, "_token": token} ``` -Common HMAC pitfalls: - -- Python's `json.dumps` adds spaces by default (`{"key": "val"}`). Use - `separators=(",", ":")` to match JavaScript's compact output (`{"key":"val"}`). -- Always sign **all** context fields (minus `_token`). The gateway strips `_token` then - signs everything remaining. Signing a subset causes silent verification failure. -- Use `sort_keys=True` — the gateway sorts keys before signing, and Mattermost may - reorder context fields when storing the payload. -- Derive the secret from the bot token (deterministic), not random bytes. The secret - must be the same across the process that creates buttons and the gateway that verifies. + + + - Python's `json.dumps` adds spaces by default (`{"key": "val"}`). Use `separators=(",", ":")` to match JavaScript's compact output (`{"key":"val"}`). + - Always sign **all** context fields (minus `_token`). The gateway strips `_token` then signs everything remaining. Signing a subset causes silent verification failure. + - Use `sort_keys=True` — the gateway sorts keys before signing, and Mattermost may reorder context fields when storing the payload. + - Derive the secret from the bot token (deterministic), not random bytes. The secret must be the same across the process that creates buttons and the gateway that verifies. + + ## Directory adapter -The Mattermost plugin includes a directory adapter that resolves channel and user names -via the Mattermost API. This enables `#channel-name` and `@username` targets in -`openclaw message send` and cron/webhook deliveries. +The Mattermost plugin includes a directory adapter that resolves channel and user names via the Mattermost API. This enables `#channel-name` and `@username` targets in `openclaw message send` and cron/webhook deliveries. No configuration is needed — the adapter uses the bot token from the account config. @@ -465,34 +493,38 @@ Mattermost supports multiple accounts under `channels.mattermost.accounts`: ## Troubleshooting -- No replies in channels: ensure the bot is in the channel and mention it (oncall), use a trigger prefix (onchar), or set `chatmode: "onmessage"`. -- Auth errors: check the bot token, base URL, and whether the account is enabled. -- Multi-account issues: env vars only apply to the `default` account. -- Native slash commands return `Unauthorized: invalid command token.`: OpenClaw - did not accept the callback token. Typical causes: - - slash command registration failed or only partially completed at startup - - the callback is hitting the wrong gateway/account - - Mattermost still has old commands pointing at a previous callback target - - the gateway restarted without reactivating slash commands -- If native slash commands stop working, check logs for - `mattermost: failed to register slash commands` or - `mattermost: native slash commands enabled but no commands could be registered`. -- If `callbackUrl` is omitted and logs warn that the callback resolved to - `http://127.0.0.1:18789/...`, that URL is probably only reachable when - Mattermost runs on the same host/network namespace as OpenClaw. Set an - explicit externally reachable `commands.callbackUrl` instead. -- Buttons appear as white boxes: the agent may be sending malformed button data. Check that each button has both `text` and `callback_data` fields. -- Buttons render but clicks do nothing: verify `AllowedUntrustedInternalConnections` in Mattermost server config includes `127.0.0.1 localhost`, and that `EnablePostActionIntegration` is `true` in ServiceSettings. -- Buttons return 404 on click: the button `id` likely contains hyphens or underscores. Mattermost's action router breaks on non-alphanumeric IDs. Use `[a-zA-Z0-9]` only. -- Gateway logs `invalid _token`: HMAC mismatch. Check that you sign all context fields (not a subset), use sorted keys, and use compact JSON (no spaces). See the HMAC section above. -- Gateway logs `missing _token in context`: the `_token` field is not in the button's context. Ensure it is included when building the integration payload. -- Confirmation shows raw ID instead of button name: `context.action_id` does not match the button's `id`. Set both to the same sanitized value. -- Agent doesn't know about buttons: add `capabilities: ["inlineButtons"]` to the Mattermost channel config. + + + Ensure the bot is in the channel and mention it (oncall), use a trigger prefix (onchar), or set `chatmode: "onmessage"`. + + + - Check the bot token, base URL, and whether the account is enabled. + - Multi-account issues: env vars only apply to the `default` account. + + + - `Unauthorized: invalid command token.`: OpenClaw did not accept the callback token. Typical causes: + - slash command registration failed or only partially completed at startup + - the callback is hitting the wrong gateway/account + - Mattermost still has old commands pointing at a previous callback target + - the gateway restarted without reactivating slash commands + - If native slash commands stop working, check logs for `mattermost: failed to register slash commands` or `mattermost: native slash commands enabled but no commands could be registered`. + - If `callbackUrl` is omitted and logs warn that the callback resolved to `http://127.0.0.1:18789/...`, that URL is probably only reachable when Mattermost runs on the same host/network namespace as OpenClaw. Set an explicit externally reachable `commands.callbackUrl` instead. + + + - Buttons appear as white boxes: the agent may be sending malformed button data. Check that each button has both `text` and `callback_data` fields. + - Buttons render but clicks do nothing: verify `AllowedUntrustedInternalConnections` in Mattermost server config includes `127.0.0.1 localhost`, and that `EnablePostActionIntegration` is `true` in ServiceSettings. + - Buttons return 404 on click: the button `id` likely contains hyphens or underscores. Mattermost's action router breaks on non-alphanumeric IDs. Use `[a-zA-Z0-9]` only. + - Gateway logs `invalid _token`: HMAC mismatch. Check that you sign all context fields (not a subset), use sorted keys, and use compact JSON (no spaces). See the HMAC section above. + - Gateway logs `missing _token in context`: the `_token` field is not in the button's context. Ensure it is included when building the integration payload. + - Confirmation shows raw ID instead of button name: `context.action_id` does not match the button's `id`. Set both to the same sanitized value. + - Agent doesn't know about buttons: add `capabilities: ["inlineButtons"]` to the Mattermost channel config. + + ## Related -- [Channels Overview](/channels) — all supported channels -- [Pairing](/channels/pairing) — DM authentication and pairing flow -- [Groups](/channels/groups) — group chat behavior and mention gating - [Channel Routing](/channels/channel-routing) — session routing for messages +- [Channels Overview](/channels) — all supported channels +- [Groups](/channels/groups) — group chat behavior and mention gating +- [Pairing](/channels/pairing) — DM authentication and pairing flow - [Security](/gateway/security) — access model and hardening