Skip to content

Slack

Netclaw talks to Slack over Socket Mode — outbound WebSocket connections only. No public URLs, no ingress rules, no reverse proxies. You create a Slack app, give netclaw two tokens, and it shows up in your workspace as a bot.

  • Netclaw installed and initialized (netclaw init)
  • A Slack workspace where you can install apps (some orgs restrict this to workspace admins)

The fastest path: create from a manifest. Go to api.slack.com/apps and click Create New App.

Slack Your Apps page

Select From a manifest, pick your workspace, and paste this:

{
"display_information": {
"name": "Netclaw",
"description": "AI assistant powered by Netclaw",
"background_color": "#512BD4"
},
"features": {
"bot_user": {
"display_name": "Netclaw",
"always_online": true
}
},
"oauth_config": {
"scopes": {
"bot": [
"app_mentions:read",
"channels:history",
"channels:read",
"chat:write",
"chat:write.customize",
"files:read",
"files:write",
"groups:history",
"groups:read",
"im:history",
"im:read",
"im:write",
"mpim:history",
"mpim:read",
"users:read"
]
}
},
"settings": {
"event_subscriptions": {
"bot_events": [
"app_mention",
"message.channels",
"message.groups",
"message.im",
"message.mpim"
]
},
"interactivity": {
"is_enabled": true
},
"org_deploy_enabled": false,
"socket_mode_enabled": true,
"token_rotation_enabled": false
}
}

Change the name and display_name to whatever you want your bot to be called.

After creating the app, you need two tokens:

  1. App-Level Token — Settings > Basic Information > App-Level Tokens. Click Generate Token and Scopes, name it anything, and add the connections:write scope.

Generate an app-level token dialog

Click Generate and copy the xapp-... token.

  1. Bot Token — Go to Install App in the sidebar and click Install to {Your Workspace}.

Slack Install App page

After approving, copy the xoxb-... Bot User OAuth Token from the OAuth & Permissions page.

Then invite the bot to each channel where it should respond: /invite @YourBotName

ScopeWhy
app_mentions:readReceive @-mention events
channels:historyRead message history in public channels
channels:readResolve channel names to IDs, list public channels
chat:writePost messages and replies in threads
chat:write.customizePost with custom display name/avatar
files:readDownload files shared in conversations
files:writeUpload files (agent output, attachments)
groups:historyRead message history in private channels
groups:readList private channels the bot is in
im:historyRead DM history for thread context
im:readList DM conversations
im:writeOpen DM conversations for proactive messaging
mpim:historyRead group DM history
mpim:readList group DM conversations
users:readLook up users by name or email for lookup_slack_user

Easiest path: netclaw init. Step 3 handles channel selection and token entry.

Channel selection during netclaw init

Pick Slack, paste your tokens, done.

For manual setup, store tokens with netclaw secrets:

Terminal window
netclaw secrets set Slack.BotToken xoxb-your-bot-token
netclaw secrets set Slack.AppToken xapp-your-app-token

Then enable Slack and point it at a channel in ~/.netclaw/config/netclaw.json:

{
"Slack": {
"Enabled": true,
"DefaultChannelName": "general"
}
}

Environment variables work too:

Terminal window
export NETCLAW_Slack__BotToken="xoxb-..."
export NETCLAW_Slack__AppToken="xapp-..."
FieldTypeDefaultDescription
EnabledboolfalseTurn on Slack
SocketModebooltrueMust be true. Only Socket Mode is supported.
BotTokenstringBot User OAuth Token (xoxb-...). Store with netclaw secrets set.
AppTokenstringApp-Level Token (xapp-...). Required for Socket Mode. Store with netclaw secrets set.
DefaultChannelNamestringChannel name, resolved to an ID at startup
DefaultChannelIdstringChannel ID directly (use instead of name if you prefer)
MentionOnlybooltrueOnly respond when @-mentioned
AllowDirectMessagesboolfalseAccept DMs
MentionRequiredInDmboolfalseRequire @-mention even in DMs
AllowedChannelIdsstring[][]Channel allow-list. Empty + no default = all channels denied
AllowedUserIdsstring[][]User allow-list. Empty = everyone in allowed channels is accepted
ChannelAudiencesobject{}Per-channel audience overrides. Keys are channel IDs or "dm". Values: "personal", "team", "public".

Tokens live in ~/.netclaw/config/secrets.json (encrypted at rest).

Slack ACL is default-deny. Three settings decide who talks to the bot and where.

The bot only responds in channels that pass the allow-list:

  • DefaultChannelName or DefaultChannelId allows one channel
  • AllowedChannelIds allows multiple
  • If all three are empty, every channel message is denied. This is the number one setup mistake.
{
"Slack": {
"DefaultChannelName": "openclaw",
"AllowedChannelIds": ["C0123456789", "C9876543210"]
}
}

Finding channel IDs: right-click a channel name in Slack, “View channel details,” scroll to the bottom. Slack’s help article has screenshots.

AllowedUserIds restricts who gets responses:

  • Empty (default) — everyone in allowed channels is accepted
  • Non-empty — only listed user IDs get responses, everyone else is silently dropped

Finding user IDs: click a user’s profile in Slack, open the three-dot menu, “Copy member ID.” Slack’s help article has screenshots.

Users in AllowedUserIds are treated as TrustedInternal by the security model. Everyone else is UntrustedExternal.

DMs are off by default:

{
"Slack": {
"AllowDirectMessages": true
}
}

With DMs on, users can just type normally — MentionRequiredInDm defaults to false, so no @-mention needed.

Audience is resolved per-message: Team for DMs and channels in your allow-list, Public for everything else. Override with ChannelAudiences:

{
"Slack": {
"ChannelAudiences": {
"C0123456789": "team",
"dm": "personal"
}
}
}

Security Model has the full breakdown on how audiences map to tools and permissions.

Each Slack thread is its own isolated session, and the bot always replies in-thread. Idle sessions are checkpointed and freed from memory after 1 hour.

On daemon restart, thread history is backfilled so in-progress conversations pick up where they left off.

MentionOnly: true (the default) means the bot ignores messages that don’t @-mention it. Two exceptions:

  • Thread replies — if a thread already has an active session, the bot responds to everything in that thread without needing a mention
  • File shares — attached files bypass the mention check entirely, with or without an active thread

Netclaw strips the @-mention before passing text to the LLM.

Netclaw converts LLM markdown to Slack Block Kit: headers, code blocks, blockquotes, lists, bold, italic, strikethrough, inline code, links. Everything renders natively in Slack.

When a tool call needs approval, netclaw posts a Block Kit prompt right in the thread:

Tool approval prompt in Slack showing Approve once, Approve for this chat, Approve always, and Deny buttons

Shows the tool name, the exact command, and four buttons. Only the user who triggered the request can approve. System-initiated tool calls (VerifiedAutomation) can be approved by anyone in the thread.

Typing a letter in the thread works too: A = Approve once, B = Approve for this chat, C = Approve always, D = Deny.

The LLM can initiate conversations through two built-in tools:

ToolWhat it does
send_slack_messagePosts a top-level message to a channel or DM. Takes channel_id or user_id. Respects ACL.
lookup_slack_userSearches users by name, display name, or email. Returns up to 10 matches. Filtered to AllowedUserIds if set. Cached 5 minutes.

The bot drops: empty messages (no text, no files), hidden messages, other bots’ messages, its own messages, messages with unsupported subtypes (edits, joins, bot subtype messages), DMs when AllowDirectMessages is off, and un-mentioned channel messages when MentionOnly is on and there’s no active thread.

Restart the daemon and check status:

Terminal window
netclaw daemon stop && netclaw daemon start
netclaw status

Slack should show connected. If it doesn’t, run netclaw doctor — it checks token validity and ACL config.

Then @-mention the bot in an allowed channel. If it responds, you’re set.

Common problems and fixes are in Channel Troubleshooting. The hits:

  • Connected but silentAllowedChannelIds is empty and no default channel is set, so all traffic gets denied
  • Works in some channels, not others — channel missing from AllowedChannelIds, or the bot hasn’t been invited
  • Socket Mode keeps disconnecting — the xapp-... App-Level Token may have expired or been revoked