--- summary: "Mattermost bot setup and OpenClaw config" read_when: - Setting up Mattermost - Debugging Mattermost routing title: "Mattermost" --- # Mattermost (plugin) Status: supported via 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. ## Plugin required Mattermost ships as a plugin and is not bundled with the core install. Install via CLI (npm registry): ```bash openclaw plugins install @openclaw/mattermost ``` Local checkout (when running from a git repo): ```bash openclaw plugins install ./extensions/mattermost ``` If you choose Mattermost during configure/onboarding and a git checkout is detected, OpenClaw will offer the local install path automatically. Details: [Plugins](/tools/plugin) ## Quick setup 1. Install the Mattermost plugin. 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. Minimal config: ```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. ```json5 { channels: { mattermost: { commands: { native: true, nativeSkills: true, callbackPath: "/api/channels/mattermost/command", // Use when Mattermost cannot reach the gateway directly (reverse proxy/public URL). callbackUrl: "https://gateway.example.com/api/channels/mattermost/command", }, }, }, } ``` 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 per-command tokens and fail closed when token checks fail. - 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: - `MATTERMOST_BOT_TOKEN=...` - `MATTERMOST_URL=https://chat.example.com` Env vars apply only to the **default** account (`default`). Other accounts must use config values. ## 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. Config example: ```json5 { channels: { mattermost: { chatmode: "onchar", oncharPrefixes: [">", "!"], }, }, } ``` Notes: - `onchar` still responds to explicit @mentions. - `channels.mattermost.requireMention` is honored for legacy configs but `chatmode` is preferred. ## Access control (DMs) - Default: `channels.mattermost.dmPolicy = "pairing"` (unknown senders get a pairing code). - Approve via: - `openclaw pairing list mattermost` - `openclaw pairing approve mattermost ` - Public DMs: `channels.mattermost.dmPolicy="open"` plus `channels.mattermost.allowFrom=["*"]`. ## Channels (groups) - Default: `channels.mattermost.groupPolicy = "allowlist"` (mention-gated). - Allowlist senders with `channels.mattermost.groupAllowFrom` (user IDs recommended). - `@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). ## Targets for outbound delivery Use these target formats with `openclaw message send` or cron/webhooks: - `channel:` for a channel - `user:` for a DM - `@username` for a DM (resolved via the Mattermost API) Bare IDs are treated as channels. ## Reactions (message tool) - Use `message action=react` with `channel=mattermost`. - `messageId` is the Mattermost post id. - `emoji` accepts names like `thumbsup` or `:+1:` (colons are optional). - Set `remove=true` (boolean) to remove a reaction. - Reaction add/remove events are forwarded as system events to the routed agent session. Examples: ``` message action=react channel=mattermost target=channel: messageId= emoji=thumbsup message action=react channel=mattermost target=channel: messageId= emoji=thumbsup remove=true ``` Config: - `channels.mattermost.actions.reactions`: enable/disable reaction actions (default true). - Per-account override: `channels.mattermost.accounts..actions.reactions`. ## Interactive buttons (message tool) 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: ```json5 { channels: { mattermost: { capabilities: ["inlineButtons"], }, }, } ``` Use `message action=send` with a `buttons` parameter. Buttons are a 2D array (rows of buttons): ``` message action=send channel=mattermost target=channel: buttons=[[{"text":"Yes","callback_data":"yes"},{"text":"No","callback_data":"no"}]] ``` 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"`. 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. 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`. ### 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 extension when possible; if posting raw JSON, follow these rules: **Payload structure:** ```json5 { channel_id: "", message: "Choose an option:", props: { attachments: [ { actions: [ { id: "mybutton01", // alphanumeric only — see below type: "button", // required, or clicks are silently ignored name: "Approve", // display label style: "primary", // optional: "default", "primary", "danger" integration: { url: "https://gateway.example.com/mattermost/interactions/default", context: { action_id: "mybutton01", // must match button id (for name lookup) action: "approve", // ... any custom fields ... _token: "", // see HMAC section below }, }, }, ], }, ], }, } ``` **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. 6. `context.action_id` is required — the interaction handler returns 400 without it. **HMAC token generation:** 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. Python example: ```python import hmac, hashlib, json secret = hmac.new( b"openclaw-mattermost-interactions", bot_token.encode(), hashlib.sha256 ).hexdigest() ctx = {"action_id": "mybutton01", "action": "approve"} payload = json.dumps(ctx, sort_keys=True, separators=(",", ":")) 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. ## 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. No configuration is needed — the adapter uses the bot token from the account config. ## Multi-account Mattermost supports multiple accounts under `channels.mattermost.accounts`: ```json5 { channels: { mattermost: { accounts: { default: { name: "Primary", botToken: "mm-token", baseUrl: "https://chat.example.com" }, alerts: { name: "Alerts", botToken: "mm-token-2", baseUrl: "https://alerts.example.com" }, }, }, }, } ``` ## 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. - 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.