Skip to content

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.

Sourceinternal/connectors/slack/
Keyslack
IconšŸ’¬
Tierbuiltin (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:

FieldTypePurpose
AuthModedropdownbot_token (default) or user_token — selects which token the runtime reads when making API calls.
BotTokensecretxoxb-… 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.
UserTokensecretxoxp-… user OAuth token, used when AuthMode=user_token. Set after the operator clicks Connect Account when ClientID is configured, or paste manually.
ClientIDstringSlack 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.
ClientSecretsecretSlack 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) ​

OpInputWhat it does
list_channelstypes, exclude_archived, name_contains, limit, cursorList channels visible to the bot. Paginated via cursor.
search_channelsquery, limitSubstring search by channel name (case-insensitive).
get_channel_infochannelMetadata for one channel — topic, purpose, creator, created.
get_channel_historychannel, limit, oldest, latest, cursorRecent messages. Top-level only — use get_thread_replies for threaded replies.
get_thread_replieschannel, ts, limit, cursorParent + every reply under a thread.
list_userslimit, cursorWorkspace members. Email requires users:read.email scope.
get_user_infouserProfile for one user ID.
get_user_by_emailemailResolve a workspace user by email. Pair with channel:slack.open_dm to DM them.
get_permalinkchannel, tsPermalink URL for a message ts.
get_reactionschannel+ts or file, fullRead 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) ​

OpInputWhat it does
list_fileschannel, user, ts_from, ts_to, types, limit, pageList files visible to the bot. Page-based pagination (page/pages), not cursor-based like the ops above.
get_file_infofileMetadata for one file ID — name, mimetype, size, channels, download/permalink URLs. Does not download bytes.
read_filefile, max_bytesDownload 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) ​

OpInputWhat it does
send_messagechannel, text, blocks, thread_ts, reply_broadcast, unfurl_links, mrkdwn, session_id?Post a message to a channel / DM / thread.
send_ephemeralchannel, user, text, blocks, thread_tsVisible only to user.
update_messagechannel, ts, text, blocks, session_id?Edit an existing message. Re-appends the "Sent using" footer.
delete_messagechannel, tsDelete by ts.
add_reactionchannel, ts, nameEmoji reaction (name without colons).
remove_reactionchannel, ts, nameRemove 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_id on send_message / update_message — optional field that tells wick which agent session owns this call. When set (or auto-injected via the X-Wick-Session-Id MCP 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.
  • channel accepts 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_ts is always the parent message ts — replying to a reply still uses the root ts.
  • get_channel_history returns only top-level messages. Walk thread replies with get_thread_replies against each parent ts.
  • oldest / latest are Slack ts strings ("1700000000.000100"), not RFC3339.
  • Pagination uses cursor from response_metadata.next_cursor; limit caps the per-call page (max 1000), not the total.
  • Email lookup requires the users:read.email scope; without it the profile.email field is empty in list_users output.
  • Rate limit: 1 msg/sec per channel for send_message. Bursts get queued then 429.
  • blocks overrides text for rendering, but Slack still wants non-empty text for the notification preview — always set both.
  • list_files paginates by page/pages, not cursor — different scheme from the channel/user/message read ops above.
  • ts_from / ts_to on list_files are 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:

yaml
- 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.

opDestructiveInputsReturns
send_messagenochannel, text, thread_ts?ts, channel
reply_threadnochannel, thread, textts
send_dmnouser, textts, channel
send_ephemeralnochannel, user, textts
update_messageyeschannel, ts, textts
reactnochannel, message_ts, emojiok
open_modalnotrigger_id, viewview_id, view_hash
update_modalnoview_id, view, view_hash?view_id
push_modalnotrigger_id, viewview_id, view_hash
open_dmnouserchannel
publish_homenouser_id, viewview_id
respond_urlnoresponse_url, text?, replace_original?, delete_original?, response_type?ok
yaml
- id: ask
  type: channel
  channel: slack
  op: open_modal
  args:
    trigger_id: "{{.Node.trigger.payload.trigger_id}}"
    view: "{{.Node.build_view.result}}"

Notes:

  • view is a Slack Block Kit view JSON (string or object). Build it with a transform node upstream.
  • open_modal / push_modal need a fresh trigger_id — Slack expires it ~3s after the interaction, so the path from inbound event to the modal op must be short.
  • react is 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.
Built with ā¤ļø by a developer, for developers.