Skip to main content
A Slack message node lets a flow post into a channel or DM a workspace member when execution reaches it route an escalation, alert on a survey answer, or ping a channel when an experience fires. Connecting takes one OAuth flow per workspace; after that, every agent in the workspace can drop the node into a flow. The node runs server-side, inside the same flow walk that composes the widget. The browser never sees the Slack bot token, and the send happens whether or not the end user has a widget open.

Connect the workspace

Connection is a standard OAuth v2 install. In the dashboard, open Connections, choose Slack, and complete the authorization. The backend mints a CSRF-signed state, redirects you to https://slack.com/oauth/v2/authorize, and Slack returns to /integrations/slack/callback. There the backend exchanges the code at oauth.v2.access for a bot token, encrypts it with AES-256-GCM, and stores it against your organization. The install requests exactly these bot scopes: Once connected, the connection card shows the workspace name, who installed it, and which agents currently reference a slack_message node. The bot token does not expire; FirstFlow validates it with auth.test rather than refreshing it.
The bot must be a member of any channel you post to. Slack returns not_in_channel for a channel the app hasn’t joined invite the app to the channel first. Public-channel listing works without joining, but posting does not.

How a Slack node runs

Eligibility, classification, and flow navigation are evaluated server-side; the slack_message node is one node the flow walker executes during that walk. When the walker hits the node it loads the org’s encrypted credentials, interpolates the message, and calls the Slack Web API. The send is fail-open: a failure is logged and the flow continues by default, so a Slack outage never blocks the user-facing experience. Eager vs deferred. In a single-shot guide flow the node fires the moment the walk reaches it. In a client-paced multi-step flow the node is deferred: it’s recorded against the content step it precedes and fires only when the user advances to that step with flow.next. This keeps “step 3 posts to Slack” honest the post happens when the user actually arrives at step 3, not when the flow is first composed.

Configure the node

Add a Slack message node in the flow editor (it carries the runtime icon slack_message). The node config persists these fields:
slackActionType
"channel" | "dm"
default:"\"channel\""
Whether to post to a channel or direct-message a member. Anything other than "dm" is treated as "channel".
slackChannelId
string
Target channel ID (e.g. C0123456789), required when slackActionType is "channel". Pick it from the channel list the node sends the channel ID, never the name.
slackUserId
string
Target member ID (e.g. U0123456789), required when slackActionType is "dm". The backend opens (or reuses) the IM channel for that user, then posts.
slackMessageText
string
required
The message body. Slack mrkdwn is on, so *bold*, _italic_, and `code` render. Empty (after interpolation and trim) is a no-op failure. Truncated to 4000 characters.
slackContinueOnFailure
boolean
default:"true"
Keep walking the flow when the send fails. Set false to halt the walk on a failed send use this only when the downstream steps genuinely depend on the notification.
slackRetryOnFailure
boolean
default:"false"
Retry a failed send. When true the backend makes up to 4 attempts with a short increasing backoff before giving up.

Template variables

slackMessageText supports the same two substitutions as the API Call node: {{userId}} and {{content}}. {{userId}} is the end user’s id; {{content}} is the triggering message content. Both are interpolated before the 4000-character truncation.
A channel post for the config below resolves to a chat.postMessage call against the channel ID, with the bot token attached server-side:
For a DM, switch the target and supply a member ID:

Verify

Save the flow and trigger the experience end to end. On success the backend logs slack_message node <id> sent and the message lands in the channel or DM. On a non-fatal failure you’ll see slack_message node <id> not sent: <reason> common reasons are not_connected, missing_target, empty_message, and send_failed. None of these surface to the end user; the reason exists for diagnostics only. You can also smoke-test the connection independently of any flow with the test-send endpoint, which posts to the connection’s default channel:
A 200 with { "data": { "ok": true, "ts": "1700000000.000100" } } confirms the token, scopes, and channel membership are all good.

Specific cases and limits

The bot can see public channels via channels:read but cannot post to a channel it hasn’t joined. Invite the app to the channel (/invite @YourApp) and retry. The backend maps this to a clear “invite the app to the channel first” error.
Private channels need groups:read and members need users:read. If a scope was added after install, reconnect the workspace so Slack re-grants it a missing_scope error means the installed token predates the scope.
The DM flow calls conversations.open then chat.postMessage. Bots, deactivated accounts, and the USLACKBOT system user are filtered out of the picker, and Slack rejects DMs to other bots with cannot_dm_bot.
Messages are truncated to 4000 characters after interpolation. mrkdwn is enabled, so Slack formatting applies; the node sends plain text, not Block Kit. There is no thread-reply option.
Slack bot tokens don’t expire, but they can be revoked by uninstalling the app. FirstFlow exposes a POST /integrations/slack/events endpoint (signature-verified with SLACK_SIGNING_SECRET) that acknowledges app_uninstalled and tokens_revoked events. After a revoke, sends fail with invalid_auth/token_revoked and you must reconnect.

Self-hosting

Slack is optional and degrades gracefully. If SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, and SLACK_SIGNING_SECRET aren’t set, the provider simply isn’t available and nothing else breaks. For local development, set SLACK_BOT_TOKEN to a static bot token to skip the OAuth dance entirely the callback recognizes a synthetic code and adopts the token directly. The callback’s redirect target comes from BACKEND_PUBLIC_URL. See Configuration.

Next

For HTTP delivery beyond Slack use a webhook node; to call an external API mid-flow and feed the result downstream use an API call node; for where action nodes sit in a flow see Flows & nodes.