Short answer: to connect OpenClaw 2026.10.7 to Slack you install the @openclaw/slack plugin, create a Slack app from the documented manifest, give OpenClaw a bot token plus either an app-level token (Socket Mode) or a signing secret (HTTP), and then decide three things the manifest does not decide for you: who may talk to the bot, which tools the model is offered when they do, and which actions need a human click. In my test the defaults handled the first question mostly sensibly, the second one badly, and the third not at all.
Updated October 2026. Everything I describe running below was run on 26 October 2026 on Ubuntu 24.04.4 LTS (4 vCPUs, 7.6 GB RAM) with OpenClaw 2026.10.7 (build 157d8c2) and @openclaw/slack 2026.10.7, on Node 24.22.1. I did not have a Slack workspace for this. Slack was a small program I wrote, so read the section "What this test is, and is not" before you trust any line of it.
1. The problem: setup guides stop when the bot says hello
Search for an OpenClaw Slack setup and you get the official pages plus a stack of walkthroughs that end at "mention the bot and it replies". That is the easy ten minutes. The questions that decide whether you can leave it running are different. Who exactly can reach it? When they do, can the model read your files and run shell commands on the host? And if something does need a human to say yes, where does that yes happen?
I wanted answers to those from the software itself, not from a feature list. So I built a harness that lets the real OpenClaw gateway and the real Slack plugin talk to a fake Slack and a fake model, drove it with signed events, and logged every call it made. This post is the build log, the numbers, and a starting configuration. It sits next to my OpenClaw self-hosting guide, which covers the gateway itself, and the OpenClaw vs Hermes Agent comparison, which covers whether to pick it at all.
2. What this test is, and is not
The OpenClaw Slack plugin reads a SLACK_API_URL environment variable and sends its Web API calls there instead of to slack.com. I pointed it at a Python server of about 50 lines that answers the calls the plugin makes (auth.test, chat.postMessage, conversations.info, users.info, reactions.add, a few more) and writes each one to a log file. For inbound traffic I used the plugin's HTTP mode and sent events to the gateway myself, signed with HMAC-SHA256 the way Slack's request-signing documentation describes. For the model I ran a second tiny server that speaks the OpenAI chat-completions format and either returns a fixed sentence or, when I ask, returns a scripted tool call.
So what you can trust here is OpenClaw's behavior when it is handed Slack-shaped input: which events it accepts, who it answers, which tools it offers the model, and what it does with a tool call. What you cannot trust is anything about Slack itself. I did not test Socket Mode, a real app install, real scope enforcement, rate limits, Enterprise Grid, threads, files, slash commands or streaming replies. Every Slack response in this post was written by me. Where I quote the OpenClaw documentation instead of my own run, I say so and link the page.
3. Pick the transport first
OpenClaw's transport page says Socket Mode and HTTP Request URLs reach feature parity for messaging, slash commands, App Home and interactivity, and that you should choose by deployment shape. The table below is my reading of it, not a test result:
| Question | Socket Mode (default) | HTTP Request URLs |
|---|---|---|
| Public URL for the gateway | Not needed | Needed, with DNS and TLS |
| Network direction | Outbound WSS to Slack must work | Inbound HTTPS from Slack must work |
| Credentials | Bot token plus app-level token with connections:write | Bot token plus signing secret |
| Several gateways, one app | Separate Slack apps per gateway | Replicas can share one app behind a load balancer |
| Slash commands | Arrive over the socket; url field ignored | Slack posts to the url field, which is required |
I used HTTP mode for one reason: I can send a signed POST from a script, and I cannot open a Slack websocket from one. If your gateway is a single VPS that can reach Slack outbound, the documentation's own advice points at Socket Mode. The access and tool findings below do not depend on the transport, because the checks run after an event is accepted. The signature findings in section 6 do.
4. Scopes: Minimal or Recommended
The setup page ships two manifests per transport. Recommended requests 23 bot scopes. Minimal requests 12: app_mentions:read, assistant:write, channels:history, channels:read, chat:write, commands, groups:history, groups:read, im:history, im:read, im:write and users:read. The page says Minimal drops files, reactions, pins, group DMs, emoji:read and usergroups:read, and that it suits workspaces whose policy restricts scopes.
For a bounded workflow, I would start with Minimal. The one place it might bite is the status reaction the bot adds while it works. In my run the plugin added an eyes reaction when it accepted a mention and an hourglass while the model ran, even though I had set actions.reactions to false, which suggests that setting gates the agent's reaction tool, not the plugin's own acknowledgement. Minimal has no reactions:write, so I made my fake Slack answer every reactions.* call with missing_scope and mentioned the bot again. The reply was still delivered. That is a simulated result, and a real Slack error may not look like mine, but it suggests a missing reaction scope is cosmetic, not fatal.
One scope-adjacent detail that I only read, not ran: the documentation says to follow up in a thread without mentioning the bot, you must subscribe to message.channels (public) or message.groups (private) with the matching history scope, because subscribing to app_mention alone cannot deliver unmentioned follow-ups. Both manifests include those subscriptions.
5. Install and the first config
I reused the isolation method from the self-hosting guide: an empty HOME, npm --prefix installs, and a non-default port. OpenClaw 2026.10.7 needs Node 24.16+ or 26.1+, so the system Node 22 would have failed at install time, as it did on 2026.10.5 in my last guide.
$ npm --prefix /tmp/oc-lab2/node24 install node@24 # 5.6 s
$ npm --prefix /tmp/oc-lab2/p24 install openclaw@2026.10.7 # 27.6 s
$ openclaw --version
OpenClaw 2026.10.7 (157d8c2)
$ openclaw plugins install @openclaw/slack
Installed plugin: slack
Saved for the next Gateway start.
The plugin was not bundled in the main package. Installing it recorded an unpinned npm spec, which the security audit later flagged (section 11). The Node runtime, OpenClaw, the plugin and the state directory together took about 0.9 GB of disk.
I then applied the documented HTTP-mode patch. The documentation recommends SecretRefs that read tokens from the environment, so no secret sits in the config file, and it recommends a dry run first:
$ export SLACK_BOT_TOKEN=xoxb-LAB-0000-fake
$ export SLACK_SIGNING_SECRET=<random string>
$ openclaw config patch --file ./slack.http.patch.json5 --dry-run
Dry run successful: 14 update(s) validated against ~/.openclaw/openclaw.json.
$ openclaw config patch --file ./slack.http.patch.json5
Applied 14 config update(s). Change will apply without restarting the gateway.
The gateway took about 14 seconds to start listening, and the Slack provider registered its route one second later:
[gateway] http server listening (15 plugins: ... slack ...; 14.0s)
[slack] [default] starting provider
[slack] http mode listening at /slack/events
$ openclaw channels status --probe
- Slack default: enabled, configured, running, connected, bot:config, works
Before that, I broke the config three ways to see what the validator catches. A typo (groupPolcy) failed the dry run with must not have additional properties, which is useful. Two real mistakes passed it: HTTP mode with no signing secret, and dmPolicy: "open" without the wildcard that the documentation says it requires. The first only showed up in the status output, as Slack default: enabled, not configured, bot:config. So a green dry run tells you the file is well formed, not that Slack is wired up. Read the status output.
6. Check the front door: signatures
With the route up, I sent three requests. A signed url_verification returned 200 and echoed the challenge, which is what Slack expects when you save a Request URL. A request signed with the wrong secret returned 401. A request with a valid signature but a timestamp one hour old also returned 401, and the gateway log said why:
[WARN] Failed to parse and verify the request data: Failed to verify authenticity:
x-slack-request-timestamp must differ from system time by no more than 5 minutes
or request is stale
That matters in practice. If your server clock drifts more than five minutes, Slack's genuine events would fail the same check, and the symptom is a Slack app whose Request URL worked at setup and then stopped delivering. Check the clock before you suspect the app.
7. Who can talk to it: DMs
The default dmPolicy is pairing. I sent a DM from a user the gateway had never seen. No model call happened. The plugin looked the user up and posted this back, which I am quoting from my fake Slack's log:
OpenClaw: access not configured.
Your Slack user id: U0ALICE001
Pairing code:
3F5U2WZE
Ask the bot owner to approve with:
openclaw pairing approve slack 3F5U2WZE
The model server's request counter stayed at zero. Then I approved the code on the gateway host and sent the same DM again:
$ openclaw pairing approve slack 3F5U2WZE
Approved slack sender U0ALICE001.
Command owner configured slack:U0ALICE001 (commands.ownerAllowFrom was empty).
The second DM reached the model and a reply came back. The surprise is that second output line. Approving the first Slack sender on a fresh install also made that person the command owner, because the owner list was empty. I confirmed it afterwards with openclaw config get commands.ownerAllowFrom, which printed ["slack:U0ALICE001"]. If the first person you approve is not you, you have just handed them owner-level commands. Set commands.ownerAllowFrom yourself before you approve anyone.
I also tried dmPolicy: "open". Without allowFrom: ["*"], a DM from a stranger was silently dropped: my fake Slack saw one users.info lookup and nothing else. With the wildcard, the stranger got a normal session. Section 9 shows what that session was allowed to use. The practical rule is that open is a tool-exposure decision as much as an access decision.
8. Who can talk to it: channels
Channels are governed separately. The default groupPolicy is allowlist, and the allowlist keys must be Slack channel IDs, not names. The troubleshooting page warns that name-based keys fail silently when the name lookup fails. I used IDs throughout and did not test the name-lookup failure. My config allowed one channel and required a mention there. These are the results, each from a signed event:
| What I sent | What the gateway did |
|---|---|
| Mention in a channel not on the allowlist | No model call. Posted an ephemeral message to the sender: the bot cannot reply here because the channel is not in its allowlist. |
| Plain message in the allowlisted channel, no mention | Looked the channel up, then nothing. No model call. |
| Mention in the allowlisted channel, from a paired user | Eyes reaction, history fetch, one model call, one reply posted. |
Slack's usual double delivery: a message event and an app_mention event with the same timestamp | One model call and one reply, not two. |
| Mention from a user who was never paired | Answered. Pairing covers DMs only. |
Same, after adding users: [one user ID] to that channel | Silently ignored. No reply, no ephemeral message. |
The fifth row is the one to remember. If you allow a channel, everyone in it can use the bot, whether or not you ever approved them in a DM. The sixth row is the fix, and the config change took effect without restarting the gateway; the log line was config change detected; evaluating reload (channels.slack.channels.C0OPS00001.users). The silence is also a trap. A blocked user gets no explanation, so "the bot ignores Bob" has more than one cause, and the per-channel users list is one of them.
9. What the model can touch
My model server logs the tool list it receives on each request, which is the cleanest way I found to see the tool surface a given conversation gets. Same gateway, same agent, five situations:
| Situation (my lab config at that point) | Who | Tools offered to the model |
|---|---|---|
| DM, default tool policy, after pairing approval | Command owner | 12: apply_patch, edit, exec, ls, openclaw, process, read, sessions_yield, tool_call, tool_describe, tool_search, write |
DM, open with wildcard, exec ask set to always | Stranger | 11: the same list without openclaw |
| Channel, deny list: runtime, fs, ui, nodes, cron, gateway | Command owner | 5: openclaw, sessions_yield, tool_call, tool_describe, tool_search |
| Same channel config | Non-owner member | 4: the same without openclaw |
Channel, allow list: group:messaging only | Command owner | 3: tool_call, tool_describe, tool_search |
Two things follow. First, a DM is a much wider door than a channel. The channel deny list I configured does nothing for DMs, and a paired or open DM user gets exec, write and edit on the gateway host unless you restrict that separately. Second, the deny list left a catalog behind it. tool_search, tool_describe and tool_call are a search-and-call layer over a larger set of tools, 57 by the gateway's own log line, so I tested whether it was a back door.
I had my model server return a scripted tool_call asking the catalog to run exec with a harmless echo, inside the denied channel. OpenClaw answered the model with this:
{ "status": "error", "tool": "tool_call",
"error": "Unknown tool id: exec. Did you mean: sessions_spawn, skills_read, subagents?
Use tool_search to find a tool, tool_describe to inspect it, then tool_call with the exact id or name." }
The denial held. The "did you mean" list is the part I would not ignore. It names sessions_spawn and subagents, which I did not deny and did not try. I do not know what a spawned sub-agent inherits from the channel's rules, and I am not going to guess. A deny list protects against the names you thought of. An allow list protects against the names you did not, and my allow-list run left the model with three plumbing tools and nothing else. For a channel bot, I would write the allow list.
10. Where approval happens
Out of the box, in my run, it does not. I asked OpenClaw for its effective execution policy before touching anything:
$ openclaw approvals get
Effective Policy
tools.exec security=full ask=off askFallback=deny (condensed from the table it prints)
Then I had the scripted model call exec with echo RAN-IN-DM && id -un in the paired DM. It ran immediately, with no prompt, and the model got back RAN-IN-DM followed by the Linux user the gateway runs as. The OpenClaw trust model page is consistent with that: it describes one trust boundary per gateway and says everyone who can message a tool-enabled agent shares its delegated tool authority.
Approvals exist, but you turn them on. I set tools.exec.security to allowlist, tools.exec.ask to always, and enabled Slack as a native approval client with one named approver:
{
tools: { exec: { security: "allowlist", ask: "always" } },
channels: { slack: { execApprovals: { enabled: true, approvers: ["U0ALICE001"], target: "dm" } } },
}
The same scripted exec call now stopped. My fake Slack received a DM to the approver with a Block Kit message titled "Exec approval required", the command, the agent name, the working directory, the host, and two buttons, Allow Once and Deny. Each button carried an approval ID and a decision in its value. The model's turn waited, and the plugin kept re-adding the hourglass reaction every few seconds while it waited.
Then I played the human. I sent a signed interactivity payload shaped like a Slack block_actions click, built from the buttons in the prompt. A click from a user who was not an approver was refused, with the log line interaction drop exec approval user=U0BOB00001 (not authorized) and an ephemeral reply, "You are not authorized to approve this request." The command stayed pending. A click on Allow Once from the named approver released it: the prompt message was edited to "Allowed once", and the model received RAN-AFTER-APPROVAL. A second command that I answered with Deny ended with the model receiving Exec denied (gateway id=..., user-denied): echo SHOULD-NOT-RUN, and the command never ran.
Two limits on that result. I only tested exec. The same DM session still had write and edit, and I did not check whether those prompt. And the documentation is careful about what this is: its exec-approvals page says approvals "reduce accidental execution risk" but "are not a per-user auth boundary". A button is a speed bump with an audit trail. It is not a sandbox.
11. What the built-in audit said
I ran openclaw security audit (not --deep) against the finished config. It reported 0 critical, 4 warnings and 1 info line. Two were about my lab: a 17-character gateway token and no trusted-proxy setting. One flagged the unpinned plugin install from section 5. The fourth is the one that applies to any team Slack:
security.trust_model.multi_user_heuristic
Potential multi-user setup detected (personal-assistant model warning)
- channels.slack.groupPolicy="allowlist" with configured group targets
Runtime/process tools are exposed without full sandboxing in at least one context.
OpenClaw's default security model is personal-assistant (one trusted operator boundary),
not hostile multi-tenant isolation on one shared gateway.
That block is trimmed to the relevant lines. Read it as the design limit. One gateway is fine for a team that already trusts each other. It is not the right shape for a Slack workspace that includes customers, contractors you have not vetted, or people who should not see each other's sessions. Its fix text suggests separate gateways, or agents.defaults.sandbox.mode="all" with tools.fs.workspaceOnly=true. I did not test either.
12. Symptoms I hit, and what caused them
| Symptom | What I saw | Cause and fix |
|---|---|---|
| Slack events return 401 | Failed to verify authenticity in the gateway log | Wrong signing secret, or clock more than 5 minutes off. Fix the secret or the clock. |
Status says not configured | enabled, not configured, bot:config | HTTP mode without a signing secret. The dry run passed anyway. |
Status says check failed | error:...invalid_auth beside running, connected | Slack rejected the token on auth.test. Replace the bot token. |
| Stranger DMs are ignored | One users.info call, no reply | dmPolicy: open without allowFrom: ["*"]. |
| Bot silent in a channel | No model call, no ephemeral message | Not mentioned, or a users list excludes the sender, or the channel key is not an ID (docs). |
| Bot silent, with an ephemeral notice | The notice says the channel is not in its OpenClaw channel allowlist | Add the channel ID under channels.slack.channels. |
The invalid_auth row was produced by switching my fake Slack to answer auth.test with that error and running the probe again. The channel-key-is-a-name cause in the silent-channel row comes from the documentation, not from my run.
13. The configuration I would start from
This is the config I ended the lab with, plus one change I am recommending but ran only on its own: an allow list instead of the deny list. Tokens come from the environment. User and channel IDs are placeholders.
{
channels: {
slack: {
enabled: true,
mode: "http", // or "socket" if you can reach Slack outbound
botToken: { source: "env", provider: "default", id: "SLACK_BOT_TOKEN" },
signingSecret: { source: "env", provider: "default", id: "SLACK_SIGNING_SECRET" },
webhookPath: "/slack/events",
dmPolicy: "pairing",
groupPolicy: "allowlist",
channels: {
C0XXXXXXXXX: { // channel ID, not name
enabled: true,
requireMention: true,
users: ["U0YYYYYYYYY"], // only these people, even if they are in the channel
tools: { allow: ["group:messaging"] },
},
},
execApprovals: { enabled: true, approvers: ["U0YYYYYYYYY"], target: "dm" },
},
},
commands: { ownerAllowFrom: ["slack:U0YYYYYYYYY"] }, // set BEFORE approving any pairing
tools: { exec: { security: "allowlist", ask: "always" } },
}
What this does not cover is DMs. A paired user still gets the full DM tool surface from section 9. If DMs matter, restrict who gets paired, or keep the bot channel-only by setting dmPolicy to disabled (one of the four documented values, which I did not run).
14. What I did not test
A guide that hides its gaps is worse than none. This run did not cover:
- A real Slack workspace, a real app install, real scope enforcement, and every Slack-side error message. My fake Slack always said what I told it to say.
- Socket Mode, relay mode, Enterprise Grid, user-token identity and multi-account setups.
- Threads and session keys, slash commands, file uploads, streaming previews, Agent View and charts.
- A real model. The model server returned a fixed sentence or a tool call I scripted, so I learned nothing about how well a model follows a channel's rules.
- Whether
write,edit,sessions_spawnor plugin tools prompt for approval, and what a spawned sub-agent inherits. - Sandboxing, load, restarts mid-conversation, and the name-lookup failure for channel keys.
- Anything on another version. This is 2026.10.7 on one day, and the Slack plugin changes quickly.
If you only need Slack messages triggered by business events, and not an agent that reasons over them, a workflow tool is a smaller attack surface. My n8n Slack integration guide covers that route. And if your agent will start long jobs from Slack, the restart and duplicate-action problems in durable execution for voice and AI agent campaigns apply to it.
15. When to hire help instead
The setup takes an afternoon. The decisions in sections 7 to 10 take longer, because they depend on who is in your workspace, what the host can reach, and what a wrong answer costs. If you want those decided, tested against your own tools, and handed over with a runbook, my AI agents page describes how I work. The ROI calculator and the AI readiness assessment help you judge whether it is worth doing, and consulting covers the wider plan.
Frequently Asked Questions
Do I need a public URL to connect OpenClaw to Slack?
Not for the default transport. The documentation says Socket Mode, the default, needs no public Gateway URL but does need outbound WSS to Slack. HTTP Request URLs need a public HTTPS endpoint with TLS. I tested only the HTTP route, against a local mock, so I have no first-hand result for Socket Mode.
Who can talk to an OpenClaw Slack bot by default?
In DMs, the default policy is pairing: an unknown user gets a short code and no model call happens until the bot owner approves it. In channels, the bot answers only in channels on the allowlist, and only when mentioned. In my test, an unpaired person in an allowlisted channel was still answered, because pairing covers DMs only. A per-channel users list closed that.
Can an OpenClaw bot in a Slack channel run shell commands?
Not if you deny it. With the default tool policy, the model was offered exec, write and edit in a DM. With a channel-level deny list for runtime and filesystem groups, a scripted attempt to call exec through the tool catalog returned an unknown tool error. An allow list of messaging tools only cut the offered tools to three. I did not test every tool in the catalog.
Can I approve agent actions from Slack buttons?
Yes for exec, in my test. With tools.exec.ask set to always and channels.slack.execApprovals enabled, the approver got a DM with Allow Once and Deny buttons. A click from a non-approver was rejected, and an Allow Once click from the approver released the command. I tested exec only, not file writes or plugin approvals.
Which Slack scopes does OpenClaw need?
The documentation offers two manifests. The Minimal one covers DMs, channel history, mentions and slash commands. The Recommended one adds files, reactions, pins, group DMs, emoji and user-group reads. In a simulated test where reaction calls returned missing_scope, the reply was still delivered, but a real Slack error may differ.
Ready to Get Started?
Book a free 30-minute discovery call. We'll identify your biggest opportunities and show you exactly what AI automation can do for your business.