Files
openclaw/docs/channels/slack.md
2026-04-04 22:14:15 +01:00

682 lines
22 KiB
Markdown

---
summary: "Slack setup and runtime behavior (Socket Mode + HTTP Events API)"
read_when:
- Setting up Slack or debugging Slack socket/HTTP mode
title: "Slack"
---
# Slack
Status: production-ready for DMs + channels via Slack app integrations. Default mode is Socket Mode; HTTP Events API mode is also supported.
<CardGroup cols={3}>
<Card title="Pairing" icon="link" href="/channels/pairing">
Slack DMs default to pairing mode.
</Card>
<Card title="Slash commands" icon="terminal" href="/tools/slash-commands">
Native command behavior and command catalog.
</Card>
<Card title="Channel troubleshooting" icon="wrench" href="/channels/troubleshooting">
Cross-channel diagnostics and repair playbooks.
</Card>
</CardGroup>
## Quick setup
<Tabs>
<Tab title="Socket Mode (default)">
<Steps>
<Step title="Create Slack app and tokens">
In Slack app settings:
- enable **Socket Mode**
- create **App Token** (`xapp-...`) with `connections:write`
- install app and copy **Bot Token** (`xoxb-...`)
</Step>
<Step title="Configure OpenClaw">
```json5
{
channels: {
slack: {
enabled: true,
mode: "socket",
appToken: "xapp-...",
botToken: "xoxb-...",
},
},
}
```
Env fallback (default account only):
```bash
SLACK_APP_TOKEN=xapp-...
SLACK_BOT_TOKEN=xoxb-...
```
</Step>
<Step title="Subscribe app events">
Subscribe bot events for:
- `app_mention`
- `message.channels`, `message.groups`, `message.im`, `message.mpim`
- `reaction_added`, `reaction_removed`
- `member_joined_channel`, `member_left_channel`
- `channel_rename`
- `pin_added`, `pin_removed`
Also enable App Home **Messages Tab** for DMs.
</Step>
<Step title="Start gateway">
```bash
openclaw gateway
```
</Step>
</Steps>
</Tab>
<Tab title="HTTP Events API mode">
<Steps>
<Step title="Configure Slack app for HTTP">
- set mode to HTTP (`channels.slack.mode="http"`)
- copy Slack **Signing Secret**
- set Event Subscriptions + Interactivity + Slash command Request URL to the same webhook path (default `/slack/events`)
</Step>
<Step title="Configure OpenClaw HTTP mode">
```json5
{
channels: {
slack: {
enabled: true,
mode: "http",
botToken: "xoxb-...",
signingSecret: "your-signing-secret",
webhookPath: "/slack/events",
},
},
}
```
</Step>
<Step title="Use unique webhook paths for multi-account HTTP">
Per-account HTTP mode is supported.
Give each account a distinct `webhookPath` so registrations do not collide.
</Step>
</Steps>
</Tab>
</Tabs>
## Manifest and scope checklist
<AccordionGroup>
<Accordion title="Slack app manifest example" defaultOpen>
```json
{
"display_information": {
"name": "OpenClaw",
"description": "Slack connector for OpenClaw"
},
"features": {
"bot_user": {
"display_name": "OpenClaw",
"always_online": true
},
"app_home": {
"messages_tab_enabled": true,
"messages_tab_read_only_enabled": false
},
"slash_commands": [
{
"command": "/openclaw",
"description": "Send a message to OpenClaw",
"should_escape": false
}
]
},
"oauth_config": {
"scopes": {
"bot": [
"app_mentions:read",
"assistant:write",
"channels:history",
"channels:read",
"chat:write",
"commands",
"emoji:read",
"files:read",
"files:write",
"groups:history",
"groups:read",
"im:history",
"im:read",
"im:write",
"mpim:history",
"mpim:read",
"mpim:write",
"pins:read",
"pins:write",
"reactions:read",
"reactions:write",
"users:read"
]
}
},
"settings": {
"socket_mode_enabled": true,
"event_subscriptions": {
"bot_events": [
"app_mention",
"channel_rename",
"member_joined_channel",
"member_left_channel",
"message.channels",
"message.groups",
"message.im",
"message.mpim",
"pin_added",
"pin_removed",
"reaction_added",
"reaction_removed"
]
}
}
}
```
</Accordion>
<Accordion title="Optional user-token scopes (read operations)">
If you configure `channels.slack.userToken`, typical read scopes are:
- `channels:history`, `groups:history`, `im:history`, `mpim:history`
- `channels:read`, `groups:read`, `im:read`, `mpim:read`
- `users:read`
- `reactions:read`
- `pins:read`
- `emoji:read`
- `search:read` (if you depend on Slack search reads)
</Accordion>
</AccordionGroup>
## Token model
- `botToken` + `appToken` are required for Socket Mode.
- HTTP mode requires `botToken` + `signingSecret`.
- `botToken`, `appToken`, `signingSecret`, and `userToken` accept plaintext
strings or SecretRef objects.
- Config tokens override env fallback.
- `SLACK_BOT_TOKEN` / `SLACK_APP_TOKEN` env fallback applies only to the default account.
- `userToken` (`xoxp-...`) is config-only (no env fallback) and defaults to read-only behavior (`userTokenReadOnly: true`).
- Optional: add `chat:write.customize` if you want outgoing messages to use the active agent identity (custom `username` and icon). `icon_emoji` uses `:emoji_name:` syntax.
Status snapshot behavior:
- Slack account inspection tracks per-credential `*Source` and `*Status`
fields (`botToken`, `appToken`, `signingSecret`, `userToken`).
- Status is `available`, `configured_unavailable`, or `missing`.
- `configured_unavailable` means the account is configured through SecretRef
or another non-inline secret source, but the current command/runtime path
could not resolve the actual value.
- In HTTP mode, `signingSecretStatus` is included; in Socket Mode, the
required pair is `botTokenStatus` + `appTokenStatus`.
<Tip>
For actions/directory reads, user token can be preferred when configured. For writes, bot token remains preferred; user-token writes are only allowed when `userTokenReadOnly: false` and bot token is unavailable.
</Tip>
## Actions and gates
Slack actions are controlled by `channels.slack.actions.*`.
Available action groups in current Slack tooling:
| Group | Default |
| ---------- | ------- |
| messages | enabled |
| reactions | enabled |
| pins | enabled |
| memberInfo | enabled |
| emojiList | enabled |
Current Slack message actions include `send`, `upload-file`, `download-file`, `read`, `edit`, `delete`, `pin`, `unpin`, `list-pins`, `member-info`, and `emoji-list`.
## Access control and routing
<Tabs>
<Tab title="DM policy">
`channels.slack.dmPolicy` controls DM access (legacy: `channels.slack.dm.policy`):
- `pairing` (default)
- `allowlist`
- `open` (requires `channels.slack.allowFrom` to include `"*"`; legacy: `channels.slack.dm.allowFrom`)
- `disabled`
DM flags:
- `dm.enabled` (default true)
- `channels.slack.allowFrom` (preferred)
- `dm.allowFrom` (legacy)
- `dm.groupEnabled` (group DMs default false)
- `dm.groupChannels` (optional MPIM allowlist)
Multi-account precedence:
- `channels.slack.accounts.default.allowFrom` applies only to the `default` account.
- Named accounts inherit `channels.slack.allowFrom` when their own `allowFrom` is unset.
- Named accounts do not inherit `channels.slack.accounts.default.allowFrom`.
Pairing in DMs uses `openclaw pairing approve slack <code>`.
</Tab>
<Tab title="Channel policy">
`channels.slack.groupPolicy` controls channel handling:
- `open`
- `allowlist`
- `disabled`
Channel allowlist lives under `channels.slack.channels` and should use stable channel IDs.
Runtime note: if `channels.slack` is completely missing (env-only setup), runtime falls back to `groupPolicy="allowlist"` and logs a warning (even if `channels.defaults.groupPolicy` is set).
Name/ID resolution:
- channel allowlist entries and DM allowlist entries are resolved at startup when token access allows
- unresolved channel-name entries are kept as configured but ignored for routing by default
- inbound authorization and channel routing are ID-first by default; direct username/slug matching requires `channels.slack.dangerouslyAllowNameMatching: true`
</Tab>
<Tab title="Mentions and channel users">
Channel messages are mention-gated by default.
Mention sources:
- explicit app mention (`<@botId>`)
- mention regex patterns (`agents.list[].groupChat.mentionPatterns`, fallback `messages.groupChat.mentionPatterns`)
- implicit reply-to-bot thread behavior
Per-channel controls (`channels.slack.channels.<id>`; names only via startup resolution or `dangerouslyAllowNameMatching`):
- `requireMention`
- `users` (allowlist)
- `allowBots`
- `skills`
- `systemPrompt`
- `tools`, `toolsBySender`
- `toolsBySender` key format: `id:`, `e164:`, `username:`, `name:`, or `"*"` wildcard
(legacy unprefixed keys still map to `id:` only)
</Tab>
</Tabs>
## Threading, sessions, and reply tags
- DMs route as `direct`; channels as `channel`; MPIMs as `group`.
- With default `session.dmScope=main`, Slack DMs collapse to agent main session.
- Channel sessions: `agent:<agentId>:slack:channel:<channelId>`.
- Thread replies can create thread session suffixes (`:thread:<threadTs>`) when applicable.
- `channels.slack.thread.historyScope` default is `thread`; `thread.inheritParent` default is `false`.
- `channels.slack.thread.initialHistoryLimit` controls how many existing thread messages are fetched when a new thread session starts (default `20`; set `0` to disable).
Reply threading controls:
- `channels.slack.replyToMode`: `off|first|all` (default `off`)
- `channels.slack.replyToModeByChatType`: per `direct|group|channel`
- legacy fallback for direct chats: `channels.slack.dm.replyToMode`
Manual reply tags are supported:
- `[[reply_to_current]]`
- `[[reply_to:<id>]]`
Note: `replyToMode="off"` disables **all** reply threading in Slack, including explicit `[[reply_to_*]]` tags. This differs from Telegram, where explicit tags are still honored in `"off"` mode. The difference reflects the platform threading models: Slack threads hide messages from the channel, while Telegram replies remain visible in the main chat flow.
## Ack reactions
`ackReaction` sends an acknowledgement emoji while OpenClaw is processing an inbound message.
Resolution order:
- `channels.slack.accounts.<accountId>.ackReaction`
- `channels.slack.ackReaction`
- `messages.ackReaction`
- agent identity emoji fallback (`agents.list[].identity.emoji`, else "👀")
Notes:
- Slack expects shortcodes (for example `"eyes"`).
- Use `""` to disable the reaction for the Slack account or globally.
## Text streaming
`channels.slack.streaming` controls live preview behavior:
- `off`: disable live preview streaming.
- `partial` (default): replace preview text with the latest partial output.
- `block`: append chunked preview updates.
- `progress`: show progress status text while generating, then send final text.
`channels.slack.nativeStreaming` controls Slack native text streaming when `streaming` is `partial` (default: `true`).
- A reply thread must be available for native text streaming to appear. Thread selection still follows `replyToMode`. Without one, the normal draft preview is used.
- Media and non-text payloads fall back to normal delivery.
- If streaming fails mid-reply, OpenClaw falls back to normal delivery for remaining payloads.
Use draft preview instead of Slack native text streaming:
```json5
{
channels: {
slack: {
streaming: "partial",
nativeStreaming: false,
},
},
}
```
Legacy keys:
- `channels.slack.streamMode` (`replace | status_final | append`) is auto-migrated to `channels.slack.streaming`.
- boolean `channels.slack.streaming` is auto-migrated to `channels.slack.nativeStreaming`.
## Typing reaction fallback
`typingReaction` adds a temporary reaction to the inbound Slack message while OpenClaw is processing a reply, then removes it when the run finishes. This is most useful outside of thread replies, which use a default "is typing..." status indicator.
Resolution order:
- `channels.slack.accounts.<accountId>.typingReaction`
- `channels.slack.typingReaction`
Notes:
- Slack expects shortcodes (for example `"hourglass_flowing_sand"`).
- The reaction is best-effort and cleanup is attempted automatically after the reply or failure path completes.
## Media, chunking, and delivery
<AccordionGroup>
<Accordion title="Inbound attachments">
Slack file attachments are downloaded from Slack-hosted private URLs (token-authenticated request flow) and written to the media store when fetch succeeds and size limits permit.
Runtime inbound size cap defaults to `20MB` unless overridden by `channels.slack.mediaMaxMb`.
</Accordion>
<Accordion title="Outbound text and files">
- text chunks use `channels.slack.textChunkLimit` (default 4000)
- `channels.slack.chunkMode="newline"` enables paragraph-first splitting
- file sends use Slack upload APIs and can include thread replies (`thread_ts`)
- outbound media cap follows `channels.slack.mediaMaxMb` when configured; otherwise channel sends use MIME-kind defaults from media pipeline
</Accordion>
<Accordion title="Delivery targets">
Preferred explicit targets:
- `user:<id>` for DMs
- `channel:<id>` for channels
Slack DMs are opened via Slack conversation APIs when sending to user targets.
</Accordion>
</AccordionGroup>
## Commands and slash behavior
- Native command auto-mode is **off** for Slack (`commands.native: "auto"` does not enable Slack native commands).
- Enable native Slack command handlers with `channels.slack.commands.native: true` (or global `commands.native: true`).
- When native commands are enabled, register matching slash commands in Slack (`/<command>` names), with one exception:
- register `/agentstatus` for the status command (Slack reserves `/status`)
- If native commands are not enabled, you can run a single configured slash command via `channels.slack.slashCommand`.
- Native arg menus now adapt their rendering strategy:
- up to 5 options: button blocks
- 6-100 options: static select menu
- more than 100 options: external select with async option filtering when interactivity options handlers are available
- if encoded option values exceed Slack limits, the flow falls back to buttons
- For long option payloads, Slash command argument menus use a confirm dialog before dispatching a selected value.
Default slash command settings:
- `enabled: false`
- `name: "openclaw"`
- `sessionPrefix: "slack:slash"`
- `ephemeral: true`
Slash sessions use isolated keys:
- `agent:<agentId>:slack:slash:<userId>`
and still route command execution against the target conversation session (`CommandTargetSessionKey`).
## Interactive replies
Slack can render agent-authored interactive reply controls, but this feature is disabled by default.
Enable it globally:
```json5
{
channels: {
slack: {
capabilities: {
interactiveReplies: true,
},
},
},
}
```
Or enable it for one Slack account only:
```json5
{
channels: {
slack: {
accounts: {
ops: {
capabilities: {
interactiveReplies: true,
},
},
},
},
},
}
```
When enabled, agents can emit Slack-only reply directives:
- `[[slack_buttons: Approve:approve, Reject:reject]]`
- `[[slack_select: Choose a target | Canary:canary, Production:production]]`
These directives compile into Slack Block Kit and route clicks or selections back through the existing Slack interaction event path.
Notes:
- This is Slack-specific UI. Other channels do not translate Slack Block Kit directives into their own button systems.
- The interactive callback values are OpenClaw-generated opaque tokens, not raw agent-authored values.
- If generated interactive blocks would exceed Slack Block Kit limits, OpenClaw falls back to the original text reply instead of sending an invalid blocks payload.
## Exec approvals in Slack
Slack can act as a native approval client with interactive buttons and interactions, instead of falling back to the Web UI or terminal.
- Exec approvals use `channels.slack.execApprovals.*` for native DM/channel routing.
- Plugin approvals can still resolve through the same Slack-native button surface when the request already lands in Slack and the approval id kind is `plugin:`.
- Approver authorization is still enforced: only users identified as approvers can approve or deny requests through Slack.
This uses the same shared approval button surface as other channels. When `interactivity` is enabled in your Slack app settings, approval prompts render as Block Kit buttons directly in the conversation.
When those buttons are present, they are the primary approval UX; OpenClaw
should only include a manual `/approve` command when the tool result says chat
approvals are unavailable or manual approval is the only path.
Config path:
- `channels.slack.execApprovals.enabled`
- `channels.slack.execApprovals.approvers` (optional; falls back to `commands.ownerAllowFrom` when possible)
- `channels.slack.execApprovals.target` (`dm` | `channel` | `both`, default: `dm`)
- `agentFilter`, `sessionFilter`
Slack auto-enables native exec approvals when `enabled` is unset or `"auto"` and at least one
approver resolves. Set `enabled: false` to disable Slack as a native approval client explicitly.
Set `enabled: true` to force native approvals on when approvers resolve.
Default behavior with no explicit Slack exec approval config:
```json5
{
commands: {
ownerAllowFrom: ["slack:U12345678"],
},
}
```
Explicit Slack-native config is only needed when you want to override approvers, add filters, or
opt into origin-chat delivery:
```json5
{
channels: {
slack: {
execApprovals: {
enabled: true,
approvers: ["U12345678"],
target: "both",
},
},
},
}
```
Shared `approvals.exec` forwarding is separate. Use it only when exec approval prompts must also
route to other chats or explicit out-of-band targets. Shared `approvals.plugin` forwarding is also
separate; Slack-native buttons can still resolve plugin approvals when those requests already land
in Slack.
Same-chat `/approve` also works in Slack channels and DMs that already support commands. See [Exec approvals](/tools/exec-approvals) for the full approval forwarding model.
## Events and operational behavior
- Message edits/deletes/thread broadcasts are mapped into system events.
- Reaction add/remove events are mapped into system events.
- Member join/leave, channel created/renamed, and pin add/remove events are mapped into system events.
- `channel_id_changed` can migrate channel config keys when `configWrites` is enabled.
- Channel topic/purpose metadata is treated as untrusted context and can be injected into routing context.
- Thread starter and initial thread-history context seeding are filtered by configured sender allowlists when applicable.
- Block actions and modal interactions emit structured `Slack interaction: ...` system events with rich payload fields:
- block actions: selected values, labels, picker values, and `workflow_*` metadata
- modal `view_submission` and `view_closed` events with routed channel metadata and form inputs
## Configuration reference pointers
Primary reference:
- [Configuration reference - Slack](/gateway/configuration-reference#slack)
High-signal Slack fields:
- mode/auth: `mode`, `botToken`, `appToken`, `signingSecret`, `webhookPath`, `accounts.*`
- DM access: `dm.enabled`, `dmPolicy`, `allowFrom` (legacy: `dm.policy`, `dm.allowFrom`), `dm.groupEnabled`, `dm.groupChannels`
- compatibility toggle: `dangerouslyAllowNameMatching` (break-glass; keep off unless needed)
- channel access: `groupPolicy`, `channels.*`, `channels.*.users`, `channels.*.requireMention`
- threading/history: `replyToMode`, `replyToModeByChatType`, `thread.*`, `historyLimit`, `dmHistoryLimit`, `dms.*.historyLimit`
- delivery: `textChunkLimit`, `chunkMode`, `mediaMaxMb`, `streaming`, `nativeStreaming`
- ops/features: `configWrites`, `commands.native`, `slashCommand.*`, `actions.*`, `userToken`, `userTokenReadOnly`
## Troubleshooting
<AccordionGroup>
<Accordion title="No replies in channels">
Check, in order:
- `groupPolicy`
- channel allowlist (`channels.slack.channels`)
- `requireMention`
- per-channel `users` allowlist
Useful commands:
```bash
openclaw channels status --probe
openclaw logs --follow
openclaw doctor
```
</Accordion>
<Accordion title="DM messages ignored">
Check:
- `channels.slack.dm.enabled`
- `channels.slack.dmPolicy` (or legacy `channels.slack.dm.policy`)
- pairing approvals / allowlist entries
```bash
openclaw pairing list slack
```
</Accordion>
<Accordion title="Socket mode not connecting">
Validate bot + app tokens and Socket Mode enablement in Slack app settings.
If `openclaw channels status --probe --json` shows `botTokenStatus` or
`appTokenStatus: "configured_unavailable"`, the Slack account is
configured but the current runtime could not resolve the SecretRef-backed
value.
</Accordion>
<Accordion title="HTTP mode not receiving events">
Validate:
- signing secret
- webhook path
- Slack Request URLs (Events + Interactivity + Slash Commands)
- unique `webhookPath` per HTTP account
If `signingSecretStatus: "configured_unavailable"` appears in account
snapshots, the HTTP account is configured but the current runtime could not
resolve the SecretRef-backed signing secret.
</Accordion>
<Accordion title="Native/slash commands not firing">
Verify whether you intended:
- native command mode (`channels.slack.commands.native: true`) with matching slash commands registered in Slack
- or single slash command mode (`channels.slack.slashCommand.enabled: true`)
Also check `commands.useAccessGroups` and channel/user allowlists.
</Accordion>
</AccordionGroup>
## Related
- [Pairing](/channels/pairing)
- [Groups](/channels/groups)
- [Security](/gateway/security)
- [Channel routing](/channels/channel-routing)
- [Troubleshooting](/channels/troubleshooting)
- [Configuration](/gateway/configuration)
- [Slash commands](/tools/slash-commands)