Connect Mattermost to OpenClaw: 5-Minute Setup Guide
Plugin required
Section titled “Plugin required”Mattermost works as a plugin and isn’t included in the core installation. You can install it via the CLI from the npm registry:
openclaw plugins install @openclaw/mattermostIf you are running from a git repo and have a local checkout, use this command:
openclaw plugins install ./path/to/local/mattermost-pluginOpenClaw will automatically offer the local install path if it detects a git checkout when you run the setup.
Check out the Plugins page for more details.
Quick setup
Section titled “Quick setup”Getting started is straightforward. You just need to follow these steps:
- Install the Mattermost plugin.
- Create a Mattermost bot account and copy the bot token.
- Copy your Mattermost base URL (for example,
https://chat.example.com). - Configure OpenClaw and start the gateway.
Here is a minimal config to get you moving:
{ channels: { mattermost: { enabled: true, botToken: "mm-token", baseUrl: "https://chat.example.com", dmPolicy: "pairing", }, },}Native slash commands
Section titled “Native slash commands”Native slash commands are an opt-in feature. When you enable them, OpenClaw registers oc_* slash commands through the Mattermost API and handles 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", }, }, },}Keep these details in mind:
- The
native: "auto"setting defaults to disabled for Mattermost. You must setnative: trueto use it. - If you omit
callbackUrl, OpenClaw creates one using the gateway host, port, andcallbackPath. - For setups with multiple accounts, you can set
commandsat the top level or underchannels.mattermost.accounts.<id>.commands. Values set for specific accounts will override top-level settings. - Command callbacks use per-command tokens for validation. If a token check fails, the request fails closed for security.
- Your Mattermost server must be able to reach the callback endpoint.
- Do not set
callbackUrltolocalhostunless Mattermost is running on the same host or network namespace as OpenClaw. - Do not set
callbackUrlto your Mattermost base URL unless that URL is configured to reverse-proxy/api/channels/mattermost/commandto OpenClaw. - You can verify reachability by running
curl https://<gateway-host>/api/channels/mattermost/command. A GET request should return405 Method Not Allowedfrom OpenClaw rather than a404. - Check the Mattermost egress allowlist. If your callback targets private, tailnet, or internal addresses, you need to update Mattermost
ServiceSettings.AllowedUntrustedInternalConnectionsto include the callback host or domain. - Use host or domain entries instead of full URLs in the allowlist.
- Good:
gateway.tailnet-name.ts.net - Bad:
https://gateway.tailnet-name.ts.net
- Good:
Environment variables (default account)
Section titled “Environment variables (default account)”If you prefer using environment variables, you can set these on the gateway host:
MATTERMOST_BOT_TOKEN=...MATTERMOST_URL=https://chat.example.com
These environment variables only apply to the default account. If you are using other accounts, you must use the configuration values instead.
Chat modes
Section titled “Chat modes”Mattermost handles your DMs automatically, but you can control how it behaves in channels using the chatmode setting. You have three main options to choose from:
oncall(default): The bot responds only when you @mention it in channels.onmessage: The bot responds to every single message in the channel.onchar: The bot responds when a message starts with a specific trigger prefix.
Here is a config example:
{ channels: { mattermost: { chatmode: "onchar", oncharPrefixes: [">", "!"], }, },}A few things to keep in mind: onchar still responds to explicit @mentions. Also, while channels.mattermost.requireMention is still supported for legacy configs, you should use chatmode instead as it is the preferred method.
Threading and sessions
Section titled “Threading and sessions”You can use channels.mattermost.replyToMode to decide if channel and group replies stay in the main channel or start a thread under the post that triggered them.
off(default): It only replies in a thread if the inbound post is already in one.first: For top-level channel or group posts, it starts a thread under that post and routes the conversation to a thread-scoped session.all: This currently behaves the same asfirstfor Mattermost.- Direct messages ignore this setting and always stay non-threaded.
Config example:
{ channels: { mattermost: { replyToMode: "all", }, },}Note that thread-scoped sessions use the triggering post id as the thread root. Currently, first and all are equivalent because once Mattermost has a thread root, follow-up chunks and media continue in that same thread.
Access control (DMs)
Section titled “Access control (DMs)”By default, the system uses channels.mattermost.dmPolicy = "pairing". This means unknown senders will receive a pairing code. You can manage these approvals using the following commands:
openclaw pairing list mattermostopenclaw pairing approve mattermost <CODE>
If you want to enable public DMs, you need to set channels.mattermost.dmPolicy="open" and add channels.mattermost.allowFrom=["*"].
Channels (groups)
Section titled “Channels (groups)”For group channels, the default setting is channels.mattermost.groupPolicy = "allowlist", which is mention-gated. You can allow specific senders using channels.mattermost.groupAllowFrom; I recommend using user IDs for this.
Matching by @username can be unreliable because names change, so it is only enabled if you set channels.mattermost.dangerouslyAllowNameMatching: true. For open channels, use channels.mattermost.groupPolicy="open".
One important runtime note: if the channels.mattermost configuration is completely missing, the system falls back to groupPolicy="allowlist" for group checks. This happens even if you have a different channels.defaults.groupPolicy defined.
Targets for outbound delivery
Section titled “Targets for outbound delivery”When you use openclaw message send or set up cron jobs and webhooks, you have a few ways to specify where your message should go:
channel:<id>for a specific channeluser:<id>for a direct message@usernamefor a direct message (this gets resolved through the Mattermost API)
You should be careful with bare opaque IDs like 64ifufp.... In Mattermost, these are ambiguous because they could represent either a user ID or a channel ID.
OpenClaw handles these using a user-first approach:
- It first checks if the ID belongs to a user via
GET /api/v4/users/<id>. If that works, OpenClaw sends a DM by finding the direct channel through/api/v4/channels/direct. - If no user is found, it treats the ID as a channel ID.
If you want to be sure about what happens, you should always use the explicit user:<id> or channel:<id> prefixes.
DM channel retry
Section titled “DM channel retry”When you send a message to a Mattermost DM target, OpenClaw often has to resolve the direct channel first. If that initial creation fails due to a temporary issue, the system retries the attempt by default.
You can adjust this behavior globally for the Mattermost plugin using channels.mattermost.dmChannelRetry, or set it for a specific account with channels.mattermost.accounts.<id>.dmChannelRetry.
{ channels: { mattermost: { dmChannelRetry: { maxRetries: 3, initialDelayMs: 1000, maxDelayMs: 10000, timeoutMs: 30000, }, }, },}A few things to keep in mind:
- This retry logic only applies to DM channel creation (
/api/v4/channels/direct). It doesn’t affect every single Mattermost API call. - Retries happen for transient failures. This includes rate limits, 5xx server responses, network errors, and timeout errors.
- If you get a 4xx client error (other than a
429rate limit), OpenClaw treats it as a permanent failure and won’t retry.
Reactions (message tool)
Section titled “Reactions (message tool)”If you want your agent to interact with posts using emojis, you can use the message action=react tool. You just need to specify channel=mattermost and provide the messageId, which is the unique ID of the Mattermost post.
For the emoji parameter, you can use standard names like thumbsup or include colons like :+1:. If you need to retract a reaction, just set remove=true. When users add or remove reactions, these actions are sent back to the agent session as system events so your bot stays in the loop.
Examples:
message action=react channel=mattermost target=channel:<channelId> messageId=<postId> emoji=thumbsupmessage action=react channel=mattermost target=channel:<channelId> messageId=<postId> emoji=thumbsup remove=trueYou can control this feature in your configuration. By default, channels.mattermost.actions.reactions is set to true. If you are running multiple accounts, you can also set an override for a specific account using channels.mattermost.accounts.<id>.actions.reactions.
Interactive buttons (message tool)
Section titled “Interactive buttons (message tool)”You can make your messages more interactive by adding clickable buttons. When a user clicks one, your agent receives the selection and can decide how to respond.
To get started, you need to enable buttons by adding inlineButtons to your channel capabilities in the config:
{ channels: { mattermost: { capabilities: ["inlineButtons"], }, },}When you want to send buttons, use message action=send with a buttons parameter. Buttons are organized in a 2D array, which allows you to group them into rows:
message action=send channel=mattermost target=channel:<channelId> buttons=[[{"text":"Yes","callback_data":"yes"},{"text":"No","callback_data":"no"}]]Each button requires a text label and callback_data, which acts as the action ID. You can also use the style field to set the look to "default", "primary", or "danger".
Once a user clicks a button, Mattermost replaces all buttons in that message with a confirmation line, such as ”✓ Yes selected by @user”. Your agent then receives that selection as an inbound message.
A few things to keep in mind:
- Button callbacks use automatic HMAC-SHA256 verification.
- Mattermost removes all buttons on click because it strips callback data from API responses for security.
- If your action IDs have hyphens or underscores, the system handles the sanitization automatically to avoid routing issues.
For configuration, use channels.mattermost.capabilities to enable the tool description in the system prompt. If your Mattermost server cannot reach the gateway directly, set channels.mattermost.interactions.callbackBaseUrl to an external URL. If you leave this out, the system tries to build a URL from your bind host and port.
If your setup is internal or uses a private network, you must add the host to the Mattermost ServiceSettings.AllowedUntrustedInternalConnections list.
Direct API integration (external scripts)
Section titled “Direct API integration (external scripts)”If you are using external scripts or webhooks, you can post buttons directly through the Mattermost REST API. While using buildButtonAttachments() is recommended, you can also post raw JSON by following these specific 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 }, }, }, ], }, ], },}Critical rules:
- Put attachments in
props.attachments. If you put them at the top level, Mattermost ignores them. - You must include
type: "button"for every action. - Every action needs an
idthat is strictly alphanumeric. Hyphens or underscores will cause 404 errors in Mattermost routing. - Ensure
context.action_idmatches the buttonidso the confirmation message displays the correct button name.
HMAC token generation:
The gateway uses HMAC-SHA256 to verify that button clicks are legitimate. Your external scripts need to generate tokens that match this logic:
- Create a secret by hashing your bot token with the key
"openclaw-mattermost-interactions". - Prepare your context object with all fields except
_token. - Serialize the object with sorted keys and no extra spaces.
- Sign that string using your secret and add the hex digest as
_token.
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}To avoid verification failures, make sure you use separators=(",", ":") in Python to match the compact JSON format used by JavaScript. Always sign every field in the context and use sort_keys=True to keep the order consistent.
Directory adapter
Section titled “Directory adapter”You don’t want to hunt for internal IDs every time you send a message. The Mattermost plugin includes a directory adapter that handles name resolution through the Mattermost API. This lets you use #channel-name and @username targets directly in openclaw message send or your cron and webhook deliveries. You don’t need to worry about extra configuration because the adapter uses the bot token from your existing account setup.
Multi-account
Section titled “Multi-account”Sometimes one account isn’t enough, especially when you need to separate primary chat from automated alerts. Mattermost supports multiple accounts under the channels.mattermost.accounts key. You can define them like this:
{ 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
Section titled “Troubleshooting”If things aren’t working as expected, don’t worry. Here is a guide to help you fix the most common issues you might run into:
- No replies in channels: Make sure the bot is actually in the channel. You might need to mention it (
oncall), use a trigger prefix (onchar), or setchatmode: "onmessage". - Auth errors: Check your bot token and base URL. You should also verify that the account is enabled.
- Multi-account issues: Keep in mind that environment variables only apply to the
defaultaccount. - Buttons appear as white boxes: This usually happens when the agent sends malformed button data. You need to check that each button has both
textandcallback_datafields. - Buttons render but clicks do nothing: Check your Mattermost server config. Verify that
AllowedUntrustedInternalConnectionsincludes127.0.0.1 localhostand thatEnablePostActionIntegrationis set totrueinServiceSettings. - Buttons return 404 on click: The button
idlikely contains hyphens or underscores. Mattermost’s action router fails on non-alphanumeric IDs. You should use[a-zA-Z0-9]only. - Gateway logs
invalid _token: This indicates an HMAC mismatch. You must sign all context fields (not just a subset), use sorted keys, and use compact JSON without extra spaces. Refer to the HMAC section above for details. - Gateway logs
missing _token in context: The_tokenfield is missing from the button’s context. You need to ensure it is included when you build the integration payload. - Confirmation shows raw ID instead of button name: This happens when
context.action_iddoes not match the button’sid. You should set both to the same sanitized value. - Agent doesn’t know about buttons: You need to add
capabilities: ["inlineButtons"]to your Mattermost channel configuration.
Related
Section titled “Related”- Channels Overview — all supported channels
- Pairing — DM authentication and pairing flow
- Groups — group chat behavior and mention gating
- Channel Routing — session routing for messages
- Security — access model and hardening
OpenClaw Expert
Still stuck?
If this page didn't answer your case, ask OpenClaw Expert for step-by-step guidance.