Mattermost Slash Commands: From Quick Echo to CI Integration – A Practical Guide
Learn how to create, secure, and scale Mattermost slash commands. Walk through a minimal echo command, a CI‑pipeline trigger, and key trade‑offs for performance and security.
22 Dec 2025, 14:47 UTC

Why Slash Commands Matter
In a busy channel, typing a bot command is often faster than navigating a web UI. Slash commands let developers expose lightweight HTTP endpoints that can trigger complex workflows—CI builds, ticket creation, or data queries—directly from the Mattermost interface.
However, turning a simple UI shortcut into a production‑ready integration requires careful attention to definition, verification, and scaling. The following guide walks through a concrete example, shows how to secure the endpoint, and highlights trade‑offs that affect performance and reliability.
Key Takeaway
By defining a command in the system console, validating incoming requests with a token or HMAC, and handling responses asynchronously, you can build robust slash commands that respect Mattermost’s rate limits and concurrency limits while keeping the user experience snappy.
Defining the Command in Mattermost
1. Open the System Console (requires System Admin role).
2. Navigate to Integrations > Slash Commands.
3. Click Add Slash Command and fill in the JSON payload.
{
"trigger_word": "echo",
"url": "https://example.com/echo",
"display_name": "Echo Bot",
"description": "Echoes the arguments back to the channel.",
"autocomplete": true,
"autocomplete_desc": "Usage: /echo [text]",
"autocomplete_args": "[text]"
}
Save the command. Mattermost now listens for /echo in any channel where the user has permission.
Building the Command Handler
Below is a minimal Flask app that validates the request and echoes the arguments. It demonstrates two key concepts:
- Verification – using the
Mattermost-Verification-Tokenheader. - Immediate vs. Delayed Response – responding with a quick “Processing…” message and posting the final result later.
from flask import Flask, request, jsonify
import requests
import os
app = Flask(__name__)
VERIFY_TOKEN = os.getenv("MM_VERIFY_TOKEN") # set in env
@app.route("/echo", methods=["POST"])
def echo():
# 1. Verify token
token = request.headers.get("Mattermost-Verification-Token")
if token != VERIFY_TOKEN:
return jsonify({"error": "Invalid token"}), 403
# 2. Parse arguments
data = request.json
args = data.get("text", "")
# 3. Immediate acknowledgement – prevents UI timeout
response = {
"response_type": "ephemeral",
"text": f"Processing… {args}"
}
# Send the immediate response
# Mattermost expects a JSON body; Flask handles this
# 4. Simulate a long task (e.g., 5 s delay)
import time
time.sleep(5)
# 5. Post the final result via the /post API
post_url = data["response_url"]
requests.post(post_url, json={"text": f"Echo: {args}"})
return jsonify(response)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Deploy this script behind a TLS‑enabled reverse proxy (e.g., Nginx) and expose https://example.com/echo to Mattermost. The response_url field is a temporary URL that lets the bot post back to the same channel.
Security Checklist
- Verification Token: Store the token securely (e.g., environment variable). Never hard‑code it in source control.
- TLS Everywhere: Mattermost requires HTTPS for slash command URLs. Self‑signed certificates are acceptable for testing but not for production.
- Rate Limiting: Use
max_concurrent_command_threads(default 10) to prevent a flood of requests from exhausting worker threads. - Permission Checks: The command definition can restrict to specific teams or users. Additionally, your handler should re‑validate the user’s role if the action is privileged.
- Input Sanitization: The
textpayload is user‑supplied. Escape or validate before rendering to avoid XSS or injection attacks.
Scaling Considerations
When a command triggers a long‑running process, the UI will show the instant “Processing…” message, but the underlying server thread remains occupied until the handler completes. This can hit the max_concurrent_command_threads limit if many users fire the command simultaneously.
Typical mitigations:
- Move heavy work to a background job queue (e.g., Celery, RabbitMQ). The handler returns immediately and enqueues the task.
- Use the
response_type: "in_channel"to share the final result with everyone, while keeping the initial response short. - Implement exponential backoff in the handler if the downstream service (e.g., CI API) is slow.
Example: A CI integration that spins up a build might expose a /build command. The handler would submit the build request to the CI system, return a “Build started” message, and then poll the CI API. Once the build completes, the handler posts the status back to the channel using the response_url. This keeps the command thread free for other users.
Trade‑offs and Limitations
- Latency vs. UX: Immediate responses keep the UI responsive but limit how much data you can return. Delayed responses allow complex workflows but risk user confusion if the final result is delayed.
- Server Overhead: Each slash command consumes a worker thread until the handler finishes. Long‑running tasks can saturate the pool, causing timeouts for other commands.
- Token vs. HMAC: The legacy token method is simple but less secure than HMAC with a shared secret. Mattermost supports HMAC verification if you enable
Enable command HMAC verificationin the console. - Visibility: Ephemeral responses are only visible to the user who invoked the command. If you need to broadcast progress, use
in_channel.
Actionable Checklist
- Define the slash command in the System Console and note the
trigger_wordandURL. - Deploy a minimal endpoint that validates the verification token and returns an immediate acknowledgement.
- Move any heavy processing to a background job and use the
response_urlto post results. - Configure
max_concurrent_command_threadsand monitor server logs for timeouts. - Test with
curlusing both a valid and an invalid token to confirm the security gating. - Document the command usage in channel or in an internal wiki so users know the expected syntax.
- Periodically review the command’s permission settings to ensure no accidental privilege escalation.
With these steps, you can turn a simple slash command into a powerful, secure, and scalable integration that fits naturally into your team’s workflow.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.