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.
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).
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 registersoc_*` commands.
- OpenClaw refreshes current Mattermost command registration before accepting each callback so stale tokens from deleted or regenerated slash commands stop being accepted without a gateway restart.
- Callback validation fails closed if the Mattermost API cannot confirm the command is still current; failed validations are cached briefly, concurrent lookups are coalesced, and fresh lookup starts are rate-limited per command to bound replay pressure.
- Slash callbacks fail closed when registration failed, startup was partial, or the callback token does not match the resolved command’s registered token (a token valid for one command cannot reach upstream validation for a different command).
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 Allowedfrom OpenClaw, not404`.
Mattermost egress allowlist
If your callback targets private/tailnet/internal addresses, set Mattermost ServiceSettings.AllowedUntrustedInternalConnections to include the callback host/domain.
Per-channel mention overrides live under `channels.mattermost.groups.
.requireMentionorchannels.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).
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.
{
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 429 are treated as permanent and are not retried.
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
},
},
}
Streaming modes
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.
Streaming behavior notes
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.
Thinking-only payloads are suppressed from channel posts, including text that arrives as a > Thinking blockquote. Set /reasoning on to see thinking in other surfaces; the Mattermost final post keeps the answer only.
Send messages with clickable buttons. When a user clicks a button, the agent receives the selection and can respond.
Normal agent replies can also include semantic presentation payloads. OpenClaw renders value buttons as Mattermost interactive buttons, keeps URL buttons visible in the message text, and downgrades select menus to readable text.
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):
All buttons are replaced with a confirmation line (e.g., ”✓ Yes selected by @user”).
Agent receives the selection
The agent receives the selection as an inbound message and responds.
Implementation 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 and reachability
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.callbackBaseUrlis omitted, OpenClaw derives the callback URL fromgateway.customBindHost+gateway.port, then falls back to http://localhost:
. - Reachability rule: the button callback URL must be reachable from the Mattermost server. localhostonly 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 MattermostServiceSettings.AllowedUntrustedInternalConnections`.
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: "
”,
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: ”
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.
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.
Ensure the bot is in the channel and mention it (oncall), use a trigger prefix (onchar), or set chatmode: "onmessage".
Auth or multi-account 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 fail
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 issues
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.