Status: downloadable 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 for product details and downloads.
Install
Install Mattermost before configuring the channel:
Details: [Plugins](/docs/openclaw-docs/tools/plugin
Quick setup
```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.
{
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",
},
},
},
}
- 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`.
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
MATTERMOST_URL cannot be set from a workspace .env; see [Workspace .env files](/docs/openclaw-docs/gateway/security.
Chat modes
Mattermost responds to DMs automatically. Channel behavior is controlled by chatmode:
Config example:
{
channels: {
mattermost: {
chatmode: "onchar",
oncharPrefixes: [">", "!"],
},
},
}
Notes:
oncharstill responds to explicit @mentions.channels.mattermost.requireMentionis honored for legacy configs butchatmodeis preferred.
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.
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.all: same behavior asfirstfor Mattermost today.- Direct messages ignore this setting and stay non-threaded.
Config example:
{
channels: {
mattermost: {
replyToMode: "all",
},
},
}
Notes:
- Thread-scoped sessions use the triggering post id as the thread root.
firstandallare currently equivalent because once Mattermost has a thread root, follow-up chunks and media continue in that same thread.
Access control (DMs)
- Default:
channels.mattermost.dmPolicy = "pairing"(unknown senders get a pairing code). - Approve via:
openclaw pairing list mattermostopenclaw pairing approve mattermost <CODE>
- Public DMs:
channels.mattermost.dmPolicy="open"pluschannels.mattermost.allowFrom=["*"].
Channels (groups)
- 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.<channelId>.requireMentionorchannels.mattermost.groups["*"].requireMentionfor a default. @usernamematching is mutable and only enabled whenchannels.mattermost.dangerouslyAllowNameMatching: true.- Open channels:
channels.mattermost.groupPolicy="open"(mention-gated). - Runtime note: if
channels.mattermostis completely missing, runtime falls back togroupPolicy="allowlist"for group checks (even ifchannels.defaults.groupPolicyis set).
Example:
{
channels: {
mattermost: {
groupPolicy: "open",
groups: {
"*": { requireMention: true },
"team-channel-id": { requireMention: false },
},
},
},
}
Targets for outbound delivery
Use these target formats with openclaw message send or cron/webhooks:
channel:<id>for a channeluser:<id>for a DM@usernamefor a DM (resolved via the Mattermost API)
OpenClaw resolves them user-first:
- If the ID exists as a user (
GET /api/v4/users/<id>succeeds), OpenClaw sends a DM by resolving the direct channel via/api/v4/channels/direct. - Otherwise the ID is treated as a channel ID.
If you need deterministic behavior, always use the explicit prefixes (user:<id> / channel:<id>).
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.
Use channels.mattermost.dmChannelRetry to tune that behavior globally for the Mattermost plugin, or channels.mattermost.accounts.<id>.dmChannelRetry for one account.
{
channels: {
mattermost: {
dmChannelRetry: {
maxRetries: 3,
initialDelayMs: 1000,
maxDelayMs: 10000,
timeoutMs: 30000,
},
},
},
}
Notes:
- This applies only to DM channel creation (
/api/v4/channels/direct), not every Mattermost API call. - Retries apply to transient failures such as rate limits, 5xx responses, and network or timeout errors.
- 4xx client errors other than
429are treated as permanent and are not retried.
Preview streaming
Mattermost streams thinking, tool activity, and partial reply text into a single draft preview post that finalizes in place when the final answer is safe to send. The preview updates on the same post id instead of spamming the channel with per-chunk messages. Media/error finals cancel pending preview edits and use normal delivery instead of flushing a throwaway preview post.
Enable via channels.mattermost.streaming:
{
channels: {
mattermost: {
streaming: "partial", // off | partial | block | progress
},
},
}
Reactions (message tool)
- Use
message action=reactwithchannel=mattermost. messageIdis the Mattermost post id.emojiaccepts names likethumbsupor:+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:
{
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:
When a user clicks a button:
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:
Payload structure:
{
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
},
},
},
],
},
],
},
}
- Attachments go in
props.attachments, not top-levelattachments(silently ignored). - Every action needs
type: "button"- without it, clicks are swallowed silently. - Every action needs an
idfield - Mattermost ignores actions without IDs. - Action
idmust be alphanumeric only ([a-zA-Z0-9]). Hyphens and underscores break Mattermost's server-side action routing (returns 404). Strip them before use. context.action_idmust match the button'sidso the confirmation message shows the button name (e.g., "Approve") instead of a raw ID.context.action_idis 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:
Python example:
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}
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:
{
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
Related
- [Channel Routing](/docs/openclaw-docs/channels/channel-routing - session routing for messages
- [Channels Overview](/docs/openclaw-docs/channels - all supported channels
- [Groups](/docs/openclaw-docs/channels/groups - group chat behavior and mention gating
- [Pairing](/docs/openclaw-docs/channels/pairing - DM authentication and pairing flow
- [Security](/docs/openclaw-docs/gateway/security - access model and hardening