Slack ā
slack wraps the Slack Web API. One instance carries one set of Slack credentials (bot token + optional user OAuth) and exposes read + write ops over channels, threads, users, messages, and reactions.
This is the outbound Slack surface ā what a workflow or LLM calls to do something on Slack. The inbound surface (events arriving in real time) is the Slack channel; the two are separate modules but normally configured together. The connector row also stores per-instance OAuth app credentials for the Connect Account user-token flow.
| Source | internal/connectors/slack/ |
| Key | slack |
| Icon | š¬ |
| Tier | builtin (every wick app) |
| Health check | ā
ā Test Integration button on the row runs every API the connector depends on |
| OAuth | ā ā global app credentials live on this row |
Configs ā
The Slack row holds credentials for both the connector ops and the Slack channel. The exact field set is form-rendered from the Configs struct ā all fields are always visible in the admin form:
| Field | Type | Purpose |
|---|---|---|
AuthMode | dropdown | bot_token (default) or user_token ā selects which token the runtime reads when making API calls. |
BotToken | secret | xoxb-⦠token used by every connector op when AuthMode=bot_token. Needs files:read and reactions:read (alongside the existing scopes) to use the Files ops and get_reactions. |
UserToken | secret | xoxp-⦠user OAuth token, used when AuthMode=user_token. Set after the operator clicks Connect Account when ClientID is configured, or paste manually. |
ClientID | string | Slack OAuth App Client ID. Required to activate the Connect Account button for the user-token OAuth flow. Lives on this instance row, not in a shared server setting. |
ClientSecret | secret | Slack OAuth App Client Secret. Required for the token exchange step of the Connect Account flow. Lives on this instance row. |
OAuth app credentials (ClientID / ClientSecret) are now per-instance ā different Slack connector rows can use different Slack apps. Enable the Connect Account flow by setting both fields and enabling EnableSSO in the Access Policy section.
The Test Integration button at the top of the row runs each API the connector needs in parallel (~5s budget) and reports only failures ā auth.test, users.list, conversations.list, chat.postMessage dry-run, etc. See Channels ā¶ Integration health check for the equivalent on the channel side.
Operations (read) ā
| Op | Input | What it does |
|---|---|---|
list_channels | types, exclude_archived, name_contains, limit, cursor | List channels visible to the bot. Paginated via cursor. |
search_channels | query, limit | Substring search by channel name (case-insensitive). |
get_channel_info | channel | Metadata for one channel ā topic, purpose, creator, created. |
get_channel_history | channel, limit, oldest, latest, cursor | Recent messages. Top-level only ā use get_thread_replies for threaded replies. |
get_thread_replies | channel, ts, limit, cursor | Parent + every reply under a thread. |
list_users | limit, cursor | Workspace members. Email requires users:read.email scope. |
get_user_info | user | Profile for one user ID. |
get_user_by_email | email | Resolve a workspace user by email. Pair with channel:slack.open_dm to DM them. |
get_permalink | channel, ts | Permalink URL for a message ts. |
get_reactions | channel+ts or file, full | Read the reactions already on a message or a file ā emoji name, count, and (with full=true) reacting user IDs. Requires reactions:read. |
All read ops are connector.Op (non-destructive).
Operations (files) ā
| Op | Input | What it does |
|---|---|---|
list_files | channel, user, ts_from, ts_to, types, limit, page | List files visible to the bot. Page-based pagination (page/pages), not cursor-based like the ops above. |
get_file_info | file | Metadata for one file ID ā name, mimetype, size, channels, download/permalink URLs. Does not download bytes. |
read_file | file, max_bytes | Download a file's bytes with the bot token and return them inline ā UTF-8 text as a string, binary (images, PDFs) as base64. Refuses files over max_bytes (default 8 MiB). |
All three require the files:read scope. read_file downloads via url_private_download using the bot token as a Bearer header ā files.info alone can't fetch bytes, the download URL is auth-gated. The bot must also be a member of a channel the file was shared to, otherwise Slack answers with an HTML login page instead of the file (surfaced as a clear error, not base64'd HTML).
Operations (write ā destructive, opt-in per row) ā
| Op | Input | What it does |
|---|---|---|
send_message | channel, text, blocks, thread_ts, reply_broadcast, unfurl_links, mrkdwn, session_id? | Post a message to a channel / DM / thread. |
send_ephemeral | channel, user, text, blocks, thread_ts | Visible only to user. |
update_message | channel, ts, text, blocks, session_id? | Edit an existing message. Re-appends the "Sent using" footer. |
delete_message | channel, ts | Delete by ts. |
add_reaction | channel, ts, name | Emoji reaction (name without colons). |
remove_reaction | channel, ts, name | Remove a reaction. |
Every write op is connector.OpDestructive ā enabled by default on every new row. Admins can disable individual ops per (row, op) at /manager/connectors/slack/{id}. The MCP layer appends a destructive warning to these ops' descriptions so the LLM confirms before calling.
Quirks worth knowing ā
session_idonsend_message/update_messageā optional field that tells wick which agent session owns this call. When set (or auto-injected via theX-Wick-Session-IdMCP header), the "Sent using @bot" footer names the bot that owns the session rather than falling back to the app name. Leave it empty when calling outside an agent session.channelaccepts a channel ID (Cā¦), DM ID (Dā¦), user ID (Uā¦ā auto-opens DM), or#name(only resolves when the bot is already a member).thread_tsis always the parent message ts ā replying to a reply still uses the root ts.get_channel_historyreturns only top-level messages. Walk thread replies withget_thread_repliesagainst each parentts.oldest/latestare Slack ts strings ("1700000000.000100"), not RFC3339.- Pagination uses
cursorfromresponse_metadata.next_cursor;limitcaps the per-call page (max 1000), not the total. - Email lookup requires the
users:read.emailscope; without it theprofile.emailfield is empty inlist_usersoutput. - Rate limit: 1 msg/sec per channel for
send_message. Bursts get queued then 429. blocksoverridestextfor rendering, but Slack still wants non-emptytextfor the notification preview ā always set both.list_filespaginates bypage/pages, notcursorā different scheme from the channel/user/message read ops above.ts_from/ts_toonlist_filesare Unix seconds (e.g.1700000000), not Slack message ts strings.
Workflow integration ā
Slack ops are a common right-hand side of a workflow connector node:
- id: notify
type: connector
module: slack
op: send_message
arg_modes:
text: expression
args:
channel: "#alerts"
text: "New ticket from {{.Node.trigger.payload.user}}: {{.Node.trigger.payload.text}}"Channel-node actions ā
For Slack actions that aren't 1:1 with a plain Web API call ā modals, ephemerals, App Home, slash-command replies ā use a channel node (channel: slack) and pick the action with op. These are wired to the live Slack API and complement the connector ops above.
op | Destructive | Inputs | Returns |
|---|---|---|---|
send_message | no | channel, text, thread_ts? | ts, channel |
reply_thread | no | channel, thread, text | ts |
send_dm | no | user, text | ts, channel |
send_ephemeral | no | channel, user, text | ts |
update_message | yes | channel, ts, text | ts |
react | no | channel, message_ts, emoji | ok |
open_modal | no | trigger_id, view | view_id, view_hash |
update_modal | no | view_id, view, view_hash? | view_id |
push_modal | no | trigger_id, view | view_id, view_hash |
open_dm | no | user | channel |
publish_home | no | user_id, view | view_id |
respond_url | no | response_url, text?, replace_original?, delete_original?, response_type? | ok |
- id: ask
type: channel
channel: slack
op: open_modal
args:
trigger_id: "{{.Node.trigger.payload.trigger_id}}"
view: "{{.Node.build_view.result}}"Notes:
viewis a Slack Block Kit view JSON (string or object). Build it with atransformnode upstream.open_modal/push_modalneed a freshtrigger_idā Slack expires it ~3s after the interaction, so the path from inbound event to the modal op must be short.reactis idempotent ā re-adding an existing emoji is a no-op, not an error.
See Workflows ā¶ channel node.
See also ā
- Channels ā¶ Slack ā inbound side (events, access control, picker, hot-reload).
- Workflows ā using these ops + channel actions in a DAG.
- HTTP / REST ā fallback for any Slack Web API call wick hasn't typed yet.