mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 23:40:45 +00:00
Merged via squash.
Prepared head SHA: 4d593731be
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
363 lines
14 KiB
Markdown
363 lines
14 KiB
Markdown
---
|
|
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.<id>.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://<gateway-host>/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 <CODE>`
|
|
- 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:<id>` for a channel
|
|
- `user:<id>` 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:<channelId> messageId=<postId> emoji=thumbsup
|
|
message action=react channel=mattermost target=channel:<channelId> messageId=<postId> emoji=thumbsup remove=true
|
|
```
|
|
|
|
Config:
|
|
|
|
- `channels.mattermost.actions.reactions`: enable/disable reaction actions (default true).
|
|
- Per-account override: `channels.mattermost.accounts.<id>.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:<channelId> 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.<id>.interactions.callbackBaseUrl`.
|
|
- If `interactions.callbackBaseUrl` is omitted, OpenClaw derives the callback URL from
|
|
`gateway.customBindHost` + `gateway.port`, then falls back to `http://localhost:<port>`.
|
|
- 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: "<channelId>",
|
|
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: "<hmac>", // 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.
|