If you landed here you probably typed “OpenClaw Slack integration setup for team use” into a search bar and want the straight dope, not another marketing gloss. Here is the no-nonsense path I took to run our OpenClaw agent in Slack for the whole engineering org, including channel routing, threading, and an approval gate for anything that can break prod.
Why put OpenClaw inside Slack instead of keeping it as a personal bot?
OpenClaw already talks to WhatsApp, Telegram, and the web UI. Slack is different because:
- It’s where teams collaborate and decisions are logged.
- Message events are well-structured (timestamps, threads, files).
- You get granular scopes for security reviews—critical when the agent can run shell commands.
- The audit log API lets compliance folks sleep at night.
But a multi-user workspace also forces some design decisions: how do you keep per-user context, stop channel noise, and protect dangerous actions? The sections below cover exactly that.
Prerequisites and versions I actually used
- OpenClaw daemon v0.24.3 (requires Node.js 22+).
- Slack app platform v2 (a “manifest-based” Slack app).
- Slack workspace on the paid plan (for audit logs & admin install). Free works, but you’ll miss the logs).
- ngrok 3.5.0 for local HTTPS during development. In prod we front OpenClaw with Traefik + Let’s Encrypt.
- A ClawCloud account if you’d rather avoid local infra. Everything below still applies.
Make sure your agent’s public URL is reachable by Slack; the Events API refuses plain HTTP.
Create the Slack app that fronts OpenClaw
1. Generate a basic manifest
Browse to api.slack.com/apps → “Create New App” → “From manifest”. Drop in this minimal YAML and tweak the domain name:
_display_information_:
name: OpenClaw Bot
description: Connects our OpenClaw agent to Slack
features:
bot_user:
display_name: OpenClaw
oauth_config:
scopes:
bot:
- chat:write
- channels:history
- groups:history
- im:history
- mpim:history
- commands
- links:read
- links:write
settings:
event_subscriptions:
request_url: https://claw.yourdomain.com/slack/events
bot_events:
- message.channels
- message.groups
- message.im
- message.mpim
interactivity:
is_enabled: true
request_url: https://claw.yourdomain.com/slack/interactivity
Important bits:
chat:writeso OpenClaw can answer.commandslets us register/clawlater.- Event request URLs must be HTTPS and public; Slack verifies them right away.
2. Install the app in a sandbox workspace first
Slack will hand you a Bot User OAuth Token that looks like xoxb-234.... Copy it; OpenClaw needs it in the next step.
3. Wire the token and signing secret into OpenClaw
Add a Slack provider block in agent.config.json (or via the UI in ClawCloud → Integrations → Slack):
"providers": {
"slack": {
"token": "xoxb-234...",
"signingSecret": "8f1af...",
"appLevelToken": "xapp-1-A123...", // optional but needed for Socket Mode fallback
"verificationToken": "gone-in-v2" // leave blank; v2 uses signatures
}
}
Restart the daemon:
openclaw daemon restart
The logs should show: [slack] registered event handler on /slack/events.
Install the app for real and scope channels
When you push to the production workspace you’ll hit the first team decision: where should the bot live?
Channel vs DM model
- Private DM (each user → @OpenClaw): clean, user-specific memory, but nobody sees answers.
- #openclaw shared channel: transparent, encourages knowledge reuse, but can get noisy fast.
- Mixed: let users DM for scratch work, then summon the bot in threads inside project channels (
@OpenClaw build status?).
We ended up with the mixed model and enforced it via Slack’s allowlist channels feature so the bot only sees #openclaw plus whatever channel an admin invites it to.
From the workspace side run:
/invite @OpenClaw #openclaw
You can further limit history scopes with channels:history:read → “Only the channels the app is a member of”.
Routing messages: team commands vs personal chat
OpenClaw supports a router.js file in your agent root. The snippet below keeps personal DMs in a “user:id” memory namespace while routing channel threads to a “channel:id” namespace so context isn’t leaked:
module.exports = async ({ slackEvent }) => {
if (!slackEvent) return 'default';
if (slackEvent.channel_type === 'im') {
return `user:${slackEvent.user}`; // private brain per user
}
if (slackEvent.thread_ts) {
return `thread:${slackEvent.thread_ts}`; // keep context inside a thread
}
return `channel:${slackEvent.channel}`; // fallback to channel-wide memory
};
Drop this file, restart the daemon, and watch OpenClaw print which memory bucket it picked in DEBUG=claw:router mode.
Thread handling: stop spamming the channel
By default the agent responds in the same place it was pinged. In a busy channel that means main timeline noise. Two env vars shipped in v0.23.0 help:
SLACK_RESPOND_IN_THREAD=true– replies under the user’s original message.SLACK_AUTO_THREAD_ROOTS=#openclaw,#support– messages posted in those channels getthread_tsset automatically even if the user didn’t start a thread.
We flipped both on. Result: a short “I’m thinking… ⏳” in the main channel, heavy multi-paragraph answer gets posted in the reply thread. Users thanked us three minutes later.
Setting up approval workflows for sensitive actions
The scary demo everyone saw on launch day was @OpenClaw deploy prod. Cute in a video, horrifying in a real workspace. Here’s how we made sure prod deploys require a human thumb-up.
1. Tag the dangerous commands
OpenClaw’s action schema supports requiresApproval. In actions.yaml:
- name: deploy
description: Deploys the main branch
parameters:
environment:
type: string
enum: [staging, prod]
requiresApproval: true
2. Choose an approval backend
- Slack modal (fast, native, ships since OpenClaw v0.24.0).
- GitHub PR comment (we use this for infra actions).
I picked the Slack modal. Add to agent.config.json:
"approvals": {
"provider": "slack",
"channel": "C05J9P6L2", // #ops-approvals channel ID
"requiredRole": "admin" // Slack role name or user group
}
Now when an action with requiresApproval hits the queue, OpenClaw DMs the approvers with Approve/Reject buttons. Only after someone clicks Approve does the task executor fire.
3. Test it end-to-end
@OpenClaw deploy prod
OpenClaw answers publicly: “Pending approval from #ops-approvals (timeout 30 min)”. In #ops-approvals, I see a message block:
- Command: deploy prod
- Requested by: @alice at 2024-05-01 13:03
- [Approve] [Reject]
Click Approve, deployment webhook fires, output streams back to the original thread. No more rogue 2 AM prod deploys.
Operational tips: logging, rate limits, multi-workspace
Verbose logging
Slack payloads vanish once acknowledged. Capture them in dev:
DEBUG=claw:*,bolt:* openclaw daemon start
Turn that off in prod; Bolt can spit out 10 MB/min under high load.
Rate limits
Slack flags your bot at 50+ messages per minute. OpenClaw batches streamed responses by default but long answers still trip the limit. We added:
"slack": { "rateLimit": 40 }
in v0.24.3; set lower if you run multiple agents on the same token.
Multiple workspaces
You cannot reuse a single Slack app across workspaces if you want per-workspace approval channels and memory. Fork the manifest, generate a new token, and spin another agent instance (or use isolated ClawCloud projects). Costs us one extra vCPU but removes cross-tenant leakage risk.
What still felt rough
- Slack modals don’t support streaming updates, so long-running approvals look frozen. We poll every 5 seconds and update the message, but it’s clunky.
- No way to granularly scope
im:history—Slack forces full IM access if you want DMs. - Enterprise Grid installation needs org-wide deploy; admin review cycles were slow. We documented a self-host fallback via Socket Mode but it adds latency.
If any of these bite you, jump into the #slack-integration channel on the OpenClaw Discord—several folks already patched custom workarounds.
Next step: automate onboarding with a slash command
Once the basic integration works, add a /claw slash command that DM-invites the user to the bot, seeds a starter prompt, and links our internal handbook. It cut “how do I start?” questions to zero.
To register:
/commands → New Command
Command: /claw
Request URL: https://claw.yourdomain.com/slack/commands/claw
Usage hint: "ask <question> or /claw help"
Handle the POST in your OpenClaw routes/slash.js:
module.exports = async (req, res) => {
const { user_id, text } = req.body;
await openclaw.process({
provider: 'slack',
user: user_id,
text,
channel: user_id,
flags: { slash: true }
});
res.send(); // empty 200 per Slack spec
};
The router from earlier will drop the request into the user’s DM memory bucket automatically.
That’s it. You now have a Slack-native OpenClaw agent that understands team context, keeps threads tidy, and refuses to deploy without a second set of eyes. In practice the whole setup—from app creation to first approved deploy—took us about 90 minutes. If you hit an edge case not covered here, open an issue on GitHub; the maintainers are quick to respond.
Practical takeaway: Treat Slack as a shared shell. Give the bot the least privilege it needs, thread everything, and demand approvals for anything you wouldn’t let a junior engineer run without review.