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