Overview
The Slack transport (internal/transport/slack/) uses Socket Mode — an outbound WebSocket to Slack — so the daemon needs no public HTTP surface. Inbound events flow over the socket; outbound calls hit the standard Web API (chat.postMessage).
Two tokens
Slack Socket Mode requires two tokens with disjoint responsibilities:
| Token | Prefix | Scope | Purpose |
|---|---|---|---|
| App-level token | xapp- |
connections:write |
Opens the Socket Mode WebSocket. |
| Bot token | xoxb- |
chat:write + event subscriptions |
Sends messages, subscribes to events. |
App setup
Full step-by-step at https://app.slack.com/apps :
- Create a new app ("From scratch"). Pick a workspace.
- Enable Socket Mode (Settings → Socket Mode). Generate an app-level token with
connections:write. This is thexapp-*token. - Configure event subscriptions (Features → Event Subscriptions). Subscribe to
message.channels,message.im, or whichever channel scopes the bot should hear. You do not need a Request URL because Socket Mode delivers events over the socket instead. - Add bot scopes (Features → OAuth & Permissions). Minimum:
chat:write. Addim:history,channels:history,groups:history, ormpim:historymatching your event subscriptions. - Install the app to the workspace. The install screen hands back the
xoxb-*bot token. - Optionally record the bot's own user ID (starts with
U…). This is what rousseau uses for own-message loop prevention.
Configuration
slack:
app_token: "xapp-1-A0..."
bot_token: "xoxb-1234..."
bot_user_id: "U0123ABCD"
reply_header: ""
allowlist:
- "U0ALICE"
- "U0BOB"
| Field | Default | Effect |
|---|---|---|
app_token |
required | xapp-* app-level token with connections:write. |
bot_token |
required | xoxb-* bot token with chat:write. |
bot_user_id |
empty | Bot user's U… ID for own-message loop prevention. Optional; falls back to inspecting the bot_id field. |
reply_header |
empty | Prepended to every outbound message. |
allowlist |
[] |
Slack user IDs whose messages are handled. |
Command-line
rousseau slack \
--app-token xapp-... \
--bot-token xoxb-... \
--bot-user-id U0123ABCD
Wire format
- Inbound. Slack sends JSON envelopes over the WebSocket. rousseau ACKs the envelope, extracts the message text and sender, and hands it to the handler.
- Outbound.
POST https://slack.com/api/chat.postMessagewith{"channel": "<id>", "text": "…"}andAuthorization: Bearer <bot_token>.
OAuth scopes explained
Every scope grants a specific API surface. The scopes rousseau needs, and what breaks without them:
| Scope | Endpoint used | Broken without |
|---|---|---|
connections:write |
apps.connections.open (Socket Mode WebSocket) |
Cannot open the socket. Required. |
chat:write |
chat.postMessage |
Cannot reply to any message. Required. |
im:history |
conversations.history for DMs (indirect via events) |
Bot won't see DM contents in events. |
im:read |
im.list, DM metadata |
Cannot list open DMs. |
im:write |
conversations.open |
Cannot open a new DM (only relevant if you want the bot to DM someone unprompted). |
mpim:history, channels:history, groups:history |
Multi-party IMs / channels / private channels | Bot won't see message contents outside DMs. |
Set the scopes under OAuth & Permissions > Bot Token Scopes. Only add scopes you actually need — Slack shows a warning at install time about each scope, and end users are more likely to install a bot with a narrow permission surface.
Own-message loop prevention
Without protection, a bot that replies to messages will also see its own replies as inbound events — leading to runaway loops. Rousseau handles this via bot_user_id:
// Simplified — actual logic in internal/transport/slack/client.go
if msg.User == cfg.BotUserID {
continue // Skip: this is our own outbound message echoing back.
}
Retrieve your bot's user ID once via:
curl -H "Authorization: Bearer xoxb-your-token" \
https://slack.com/api/auth.test
The response includes user_id. Paste it into slack.bot_user_id in config, or pass with --bot-user-id.
Threading
Slack messages carry a thread_ts when they are replies in a thread. Rousseau's outbound calls include thread_ts when the inbound event had one, so bot replies stay threaded. Top-level messages become new threads only when the user starts one.
Failure modes
| Symptom | Fix |
|---|---|
invalid_auth on socket open |
app_token is wrong or missing connections:write. Regenerate. |
| Inbound events never arrive | Verify Event Subscriptions are enabled and the relevant message.* events are subscribed. |
| Bot replies to its own messages | Set bot_user_id in config. |
not_in_channel on send |
Invite the bot to the channel (/invite @rousseau-bot). |
| DM works but channel does not | Missing channels:history scope, or the bot has not been invited to the channel. |
Troubleshooting
invalid_auth on socket open
The xapp-… token is wrong or lost its scope. Regenerate from Basic Information > App-Level Tokens, ensure connections:write is on the new token.
not_authed on chat.postMessage
Bot token (xoxb-…) missing or wrong. Regenerate from OAuth & Permissions > Bot User OAuth Token.
Events arrive but rousseau responds to none of them
Check the allowlist. In pattern mode with default: deny, unlisted users get silently dropped. Look for router.transport.rejected in the logs.
channel_not_found on outbound
The Slack channel ID (C…) has changed — for example, a channel was archived and recreated. Update any hardcoded channel IDs. Rousseau normally uses the channel from the inbound event, so this only happens with cron delivery to a fixed channel.
Bot appears offline in Slack
Socket Mode idles the WebSocket every ~30s. If Slack shows the bot as offline, verify: (1) the daemon is running (systemctl --user status), (2) the WebSocket is connected (log line slack.connected), (3) the machine's clock is within 30s of true time.
Related pages
- Getting Started: First Transport — end-to-end walkthrough.
- Configuration — the
slackconfig block. - Transports — sibling transports.
- Deployment — running Slack in a Podman container.
- Guides: Audit & Approval Policies — policy rulesets for a shared Slack workspace.
Further reading
internal/transport/slack/client.go— Socket Mode connection, event pump,chat.postMessage.internal/cli/slack.go— CLI wiring.internal/transport/router.go— allowlist enforcement.- Slack API docs: Socket Mode.