Skip to content

$io.slack ​

Server API reference.

Functions ​

$io.slack.deleteMessage ​

Delete a specific Slack message using its channel ID and message timestamp (ts).

Usage ​

js
$io.slack.deleteMessage(connectionName, channel, ts, customRequestProperties)

Description ​

Wraps Slack's chat.delete to remove a normal (persistent) message from a conversation. Provide the channel ID and the exact message timestamp (ts) returned by Slack when the message was created.

Important details

  • The pair (channel, ts) uniquely identifies a message in Slack; there is no separate message ID.
  • With a bot token, you can typically delete messages posted by your app.
  • With a user token (BYO Custom OAuth2), deleting is limited to messages posted by that user, subject to workspace policies.
  • Ephemeral messages (from chat.postEphemeral) cannot be deleted because they are not stored in history.

Scopes

  • Requires chat:write. Additional admin restrictions may apply depending on workspace settings.

Usage Scenarios ​

  • Clean up automated notifications after a delay
  • Implement undo/rollback flows for bot posts
  • Remove outdated status messages from a channel

Parameters ​

connectionName ​

Type: String

Required: Yes

Slack connection name configured in ioLoop.

channel ​

Type: String

Required: Yes

Channel ID (e.g., 'C0123456789').

ts ​

Type: String | Date | moment

Required: Yes

Exact Slack timestamp of the message to delete (e.g., '1757613097.379449'). If Date/moment is provided, it is normalized to epoch seconds, but deletion should use the precise ts string returned by Slack.

customRequestProperties ​

Type: Object

Required: No

Advanced request options (pass-through). In BYO mode, may include { actor: 'bot' | 'user' } to choose bearer.

Output ​

Type: Promise

Resolves to { status: true, data, err }. On success (Slack response):\n- data.ok: true\n- data.channel: channel ID\n- data.ts: timestamp of the deleted message

$io.slack.deleteScheduledMessage ​

Delete a scheduled Slack message before it is posted.

Usage ​

js
$io.slack.deleteScheduledMessage(connectionName, channel, scheduledMessageId, customRequestProperties)

Description ​

Wraps Slack's chat.deleteScheduledMessage to cancel a scheduled post. Provide the channel ID and the scheduled message ID as returned by scheduleMessage or listScheduledMessages.

Token & scope notes

  • Requires chat:write.

Behavior

  • If the scheduled time has already passed, or the ID is invalid, Slack returns invalid_scheduled_message_id.
  • Scheduled messages are uniquely identified by scheduled_message_id plus the channel.
  • Once deleted, the message will never be posted.

Common flow

  • Use after listScheduledMessages to bulk-cancel future posts.

Usage Scenarios ​

  • Cancel a scheduled announcement that is no longer needed
  • Allow users to reschedule posts by deleting the old one
  • Clean up scheduled jobs in admin dashboards

Parameters ​

connectionName ​

Type: String

Required: Yes

Slack connection name configured in ioLoop.

channel ​

Type: String

Required: Yes

Channel ID where the scheduled message is queued.

scheduledMessageId ​

Type: String

Required: Yes

The scheduled message ID, returned by scheduleMessage or listScheduledMessages.

customRequestProperties ​

Type: Object

Required: No

Advanced request options (pass-through). In BYO mode, may include { actor: 'bot' | 'user' }.

Output ​

Type: Promise

Resolves to { status: true, data, err }. On success (Slack response):\n- data.ok: true

$io.slack.getChannelByName ​

Lookup a Slack channel by its name and return its details.

Usage ​

js
$io.slack.getChannelByName(connectionName, channelName, options, customRequestProperties)

Description ​

This function provides a convenience wrapper around conversations.list to locate a Slack channel by its name (without the leading "#"). It automatically paginates through channels until a match is found or the maximum page limit is reached.

Channel names are unique per Slack workspace (public or private). Archived channels also reserve their names, so at most one channel will match.

If found, the function returns a channel object with fields such as id, name, is_archived, is_private, and num_members. If no channel is found, data will be null.

The options parameter controls pagination and whether to include archived channels.

Usage Scenarios ​

  • Resolve a channel ID from its human-readable name
  • Validate whether a given channel exists before posting a message
  • Fetch archived channel details by name for auditing

Parameters ​

connectionName ​

Type: String

Required: Yes

The name of the Slack connection configured in the ioLoop app.

channelName ​

Type: String

Required: Yes

The Slack channel name (without leading '#'). Case-insensitive.

options ​

Type: Object

Required: No

Options to control lookup behavior.

customRequestProperties ​

Type: Object

Required: No

Advanced axios configuration overrides, headers, or actor selection. Normally null.

Output ​

Type: Promise

Returns a Promise resolving to an object with:

  • status: true if call succeeded
  • data: the channel object if found, or null if not
  • err: error message if failed

$io.slack.getConversationHistory ​

Retrieve message history for a conversation (newest first), with optional time range and pagination.

Usage ​

js
$io.slack.getConversationHistory(connectionName, channel, oldest, latest, inclusive, limit, cursor, customRequestProperties)

Description ​

Wraps Slack's conversations.history. Returns messages for a conversation (channel, group, IM, MPIM) in reverse chronological order (newest first). Supports time range filtering (oldest, latest), inclusivity, page size, and cursor-based pagination.

Token rules:

  • With a bot token, Slack only returns history for conversations the bot is a member of.
  • For public/private channels where the bot is not a member, use a user token via a Custom OAuth2 profile with appropriate scopes (e.g., channels:history, groups:history). Pass { actor: 'user' } via customRequestProperties.

Time parameters:

  • If you already have a Slack ts (e.g., "1741882130.123456"), pass it as-is.
  • Dates provided as Date or moment objects are normalized to epoch seconds ('X') before calling Slack.

Usage Scenarios ​

  • Load the most recent messages from a channel before posting a summary
  • Fetch messages within a time window for analytics or archival
  • Page backwards through older history using Slack cursors

Parameters ​

connectionName ​

Type: String

Required: Yes

Slack connection name configured in ioLoop.

channel ​

Type: String

Required: Yes

Conversation ID (e.g., 'C0123456789').

oldest ​

Type: String | Number | Date | moment

Required: No

Start of range. Accepts Slack ts string or a Date/moment/epoch value; Dates/moment are converted to epoch seconds.

latest ​

Type: String | Number | Date | moment

Required: No

End of range. Accepts Slack ts string or a Date/moment/epoch value; Dates/moment are converted to epoch seconds.

inclusive ​

Type: Boolean

Required: No

If true, include messages whose ts equals oldest or latest.

limit ​

Type: Number

Required: No

Max messages per page (Slack caps apply; commonly up to 1000).

cursor ​

Type: String

Required: No

Pagination cursor from a previous response (use data.response_metadata.next_cursor).

customRequestProperties ​

Type: Object

Required: No

Advanced request options (pass-through). May include { actor: 'bot' | 'user' } in BYO mode to select bearer.

Output ​

Type: Promise

Resolves to { status: true, data, err }. On success:

  • data.ok: true
  • data.messages: Array of message objects (ts, text, user, thread_ts, blocks, etc.) in newest-first order
  • data.has_more: boolean (when provided by Slack)
  • data.response_metadata.next_cursor: string for pagination

$io.slack.getConversationInfo ​

Get detailed information about a single conversation (channel, group, IM, or MPIM).

Usage ​

js
$io.slack.getConversationInfo(connectionName, channel, customRequestProperties)

Description ​

This function wraps Slack's conversations.info to return metadata about a specific conversation, such as id, name, is_private, is_archived, purpose, and topic.

By default the bot token is used (scopes like channels:read, groups:read, im:read, mpim:read). In BYO (Custom OAuth2) profiles, you may pass { actor: 'user' } via customRequestProperties to force a user token.

Usage Scenarios ​

  • Show channel metadata (name, privacy, archived status) before posting
  • Verify a channel ID obtained from user input or prior search
  • Fetch topic, purpose, and member-related flags to drive UI

Parameters ​

connectionName ​

Type: String

Required: Yes

The name of the Slack connection configured in ioLoop.

channel ​

Type: String

Required: Yes

The conversation ID (e.g., 'C0123456789').

customRequestProperties ​

Type: Object

Required: No

Advanced options passed through to the request. May include { actor: 'bot' | 'user' } in BYO mode.

Output ​

Type: Promise

Resolves to { status: true, data, err }. On success:

  • data.ok: true
  • data.channel: Object with fields such as id, name, is_private, is_archived, is_general, is_shared, num_members (when available), purpose, topic, and other Slack attributes.

$io.slack.getConversationMembers ​

Retrieve the list of member user IDs for a given conversation (channel, group, IM, or MPIM).

Usage ​

js
$io.slack.getConversationMembers(connectionName, channel, limit, cursor, customRequestProperties)

Description ​

Wraps Slack's conversations.members API. Returns a paginated list of user IDs belonging to the specified conversation.

Token rules:

  • With a bot token, Slack only returns members for conversations the bot is a member of. Use conversations.join or invite the app to the channel.
  • In BYO (Custom OAuth2) profiles, you can pass { actor: 'user' } in customRequestProperties to use a user token with channels:read / groups:read scopes.

Use the response_metadata.next_cursor to paginate through large channels.

Usage Scenarios ​

  • Fetch all members of a channel before sending bulk DMs
  • Check whether a specific user is part of a channel
  • Paginate through large member lists to build a roster

Parameters ​

connectionName ​

Type: String

Required: Yes

Slack connection name configured in ioLoop.

channel ​

Type: String

Required: Yes

Conversation ID (e.g., 'C0123456789').

limit ​

Type: Number

Required: No

Maximum number of members to return per page. Slack supports up to 1000.

cursor ​

Type: String

Required: No

Pagination cursor from a previous response. Use data.response_metadata.next_cursor.

customRequestProperties ​

Type: Object

Required: No

Advanced request options. May include { actor: 'bot' | 'user' } in BYO mode to control which token is used.

Output ​

Type: Promise

Resolves to { status: true, data, err }. On success:

  • data.ok: true
  • data.members: Array of user IDs (strings)
  • data.response_metadata.next_cursor: cursor string for pagination

Retrieve a permanent link (permalink) to a specific Slack message in a channel.

Usage ​

js
$io.slack.getPermalink(connectionName, channel, messageTs, customRequestProperties)

Description ​

Wraps Slack's chat.getPermalink to obtain a permanent URL for a message in a channel. Provide the channel ID and the exact message timestamp (ts) returned by Slack when the message was posted.

Key details

  • Permalinks are stable, shareable URLs that point directly to the message in Slack.
  • They remain valid even if the message is later deleted (but will show an error if the message no longer exists).
  • Requires chat:write or channels:read/equivalent read scopes, depending on the channel type.
  • Typically used together with postMessage (to create) and deleteMessage (to clean up).

Example flow

  1. Post a message with postMessage.
  2. Call getPermalink to capture the URL.
  3. Optionally delete the message while keeping the permalink for reference.

Usage Scenarios ​

  • Generate a shareable link to a message for reference outside Slack
  • Fetch a permalink before deleting or archiving the message
  • Store a direct pointer to a message for audit or logging purposes

Parameters ​

connectionName ​

Type: String

Required: Yes

Slack connection name configured in ioLoop.

channel ​

Type: String

Required: Yes

Channel ID (e.g., 'C0123456789').

messageTs ​

Type: String | Date | moment

Required: Yes

Message timestamp of the message to link (e.g., '1757613097.379449'). If Date/moment is provided, it is normalized to epoch seconds, but best practice is to use the exact ts string returned by Slack.

customRequestProperties ​

Type: Object

Required: No

Advanced request options (pass-through). In BYO mode, may include { actor: 'bot' | 'user' } to select bearer.

Output ​

Type: Promise

Resolves to { status: true, data, err }. On success (Slack response):\n- data.ok: true\n- data.channel: channel ID\n- data.permalink: the permanent URL pointing to the message.

$io.slack.getThreadReplies ​

Retrieve all messages in a specific Slack thread (root + replies), with cursor pagination.

Usage ​

js
$io.slack.getThreadReplies(connectionName, channel, threadTs, limit, cursor, customRequestProperties)

Description ​

Wraps Slack's conversations.replies to return the thread root and its replies in reverse chronological order (newest first). You must provide the root message's thread_ts. A convenient way to find one is to call $io.slack.getConversationHistory and look for a message where m.thread_ts && m.ts === m.thread_ts (root). Replies have m.thread_ts pointing back to that root.

Token & access rules:

  • With a bot token, the app must be a member of the conversation.
  • For many public/private channel threads, Slack may require a user token with channels:history / groups:history. In BYO (Custom OAuth2), pass { actor: 'user' } via customRequestProperties.
  • IM/MPIM threads generally work with bot tokens if you have im:history / mpim:history.

Notes:

  • Pagination uses response_metadata.next_cursor.
  • If you want oldest→newest order, reverse the returned array client-side.

Usage Scenarios ​

  • Expand a thread to summarize or analyze the discussion
  • Fetch replies for moderation or escalation workflows
  • Build a UI that shows threaded conversations on demand

Parameters ​

connectionName ​

Type: String

Required: Yes

Slack connection name configured in ioLoop.

channel ​

Type: String

Required: Yes

Conversation ID (e.g., 'C0123456789').

threadTs ​

Type: String | Date | moment

Required: Yes

Root message timestamp (Slack ts). If a Date/moment is provided, it is normalized to epoch seconds.

limit ​

Type: Number

Required: No

Max messages per page (Slack caps apply).

cursor ​

Type: String

Required: No

Pagination cursor from a previous response (use data.response_metadata.next_cursor).

customRequestProperties ​

Type: Object

Required: No

Advanced request options (pass-through). In BYO mode, may include { actor: 'bot' | 'user' } to select bearer.

Output ​

Type: Promise

Resolves to { status: true, data, err }. On success:

  • data.ok: true
  • data.messages: Array with the thread root and its replies (fields like ts, thread_ts, user, text, blocks, etc.), newest first
  • data.response_metadata.next_cursor: string for pagination

$io.slack.getUserInfo ​

Fetch detailed profile information for a specific Slack user.

Usage ​

js
$io.slack.getUserInfo(connectionName, user, customRequestProperties)

Description ​

Wraps Slack's users.info to retrieve a single user's profile and status by user ID (e.g., 'U0123456789').

Token & scope notes

  • Requires users:read.
  • Email in user.profile.email requires users:read.email.

Typical fields returned

  • id, team_id, name, deleted, is_bot
  • profile (e.g., real_name, display_name, email, image_*)

Usage Scenarios ​

  • Display a user’s full profile in a people panel
  • Resolve a user ID to email / real name
  • Check if a user is deactivated or a bot

Parameters ​

connectionName ​

Type: String

Required: Yes

Slack connection name configured in ioLoop.

user ​

Type: String

Required: Yes

Slack user ID to fetch (e.g., 'U0123456789').

customRequestProperties ​

Type: Object

Required: No

Advanced request options (pass-through). In BYO mode, may include { actor: 'bot' | 'user' }.

Output ​

Type: Promise

Resolves to { status: true, data, err }. On success (Slack response):\n- data.ok: true\n- data.user: the full user object

$io.slack.listConversations ​

List conversations (channels, groups, IMs, MPIMs) visible to the connection.

Usage ​

js
$io.slack.listConversations(connectionName, types, limit, cursor, excludeArchived, customRequestProperties)

Description ​

This function wraps the Slack conversations.list API. It returns channels, groups, IMs, and MPIMs based on the provided types.

By default, the connection uses the bot token (with scopes channels:read, groups:read, im:read, mpim:read). If the profile is BYO (Custom OAuth2), you may specify { actor: 'user' } in customRequestProperties to use a user token.

Results include channel metadata such as id, name, is_private, is_archived, num_members, purpose, topic, and more. Pagination is supported via Slack's response_metadata.next_cursor.

Usage Scenarios ​

  • Fetch a list of public and private channels in the workspace
  • Enumerate IMs or MPIMs for direct messaging use cases
  • Build a channel picker UI for posting or searching

Parameters ​

connectionName ​

Type: String

Required: Yes

The name of the Slack connection configured in ioLoop.

types ​

Type: String | Array<String>

Required: No

Conversation types to include. Allowed values: public_channel, private_channel, im, mpim. Accepts comma-separated string or array. Default: all types.

limit ​

Type: Number

Required: No

Maximum number of conversations to return per page. Slack supports up to 1000. Default is server-defined.

cursor ​

Type: String

Required: No

Pagination cursor from a previous response. Use response_metadata.next_cursor from the prior call.

excludeArchived ​

Type: Boolean

Required: No

If true, exclude archived conversations. Default is false.

customRequestProperties ​

Type: Object

Required: No

Advanced options passed through to the request. May include { actor: 'bot' | 'user' } to control which token is used (BYO mode only).

Output ​

Type: Promise

Resolves to { status: true, data, err }.

  • data.ok: true if Slack succeeded
  • data.channels: Array of conversation objects (id, name, is_private, num_members, purpose, topic, etc.)
  • data.response_metadata.next_cursor: cursor string for pagination

$io.slack.listFiles ​

List files in Slack with optional filters for user, channel, time range, and types; supports pagination.

Usage ​

js
$io.slack.listFiles(connectionName, user, channel, tsFrom, tsTo, types, limit, cursor, customRequestProperties)

Description ​

Wraps Slack's files.list to retrieve files visible to the token, with optional filters:

Filters

  • user: only files uploaded by a specific user (e.g., 'U0123456789')
  • channel: only files shared in a specific channel (e.g., 'C0123456789')
  • tsFrom / tsTo: inclusive time range; accepts Unix epoch seconds or Date/moment (normalized automatically)
  • types: one or more categories (comma-separated or array), e.g. 'images,spaces,pdfs,snippets,zip'

Scopes

  • Requires files:read.

Pagination

  • Use limit and pass back response_metadata.next_cursor as cursor to fetch subsequent pages.

Usage Scenarios ​

  • Browse recent uploads in a specific channel
  • Audit files shared by a particular user
  • Find PDFs/images within a date range

Parameters ​

connectionName ​

Type: String

Required: Yes

Slack connection name configured in ioLoop.

user ​

Type: String

Required: No

Filter by uploader's user ID (e.g., 'U0123456789').

channel ​

Type: String

Required: No

Filter by channel ID where the file was shared (e.g., 'C0123456789').

tsFrom ​

Type: String | Number | Date | moment

Required: No

Oldest timestamp (inclusive). Unix epoch seconds preferred; Date/moment accepted.

tsTo ​

Type: String | Number | Date | moment

Required: No

Newest timestamp (inclusive). Unix epoch seconds preferred; Date/moment accepted.

types ​

Type: String | Array

Required: No

Comma-separated list or array of file categories (e.g., 'images,spaces,pdfs').

limit ​

Type: Number

Required: No

Max results per page (Slack caps apply).

cursor ​

Type: String

Required: No

Pagination cursor from a previous call's response_metadata.next_cursor.

customRequestProperties ​

Type: Object

Required: No

Advanced request options (pass-through). In BYO mode, may include { actor: 'bot' | 'user' }.

Output ​

Type: Promise

Resolves to { status: true, data, err }. On success (Slack response):\n- data.ok: true\n- data.files: Array of file objects (id, name, mimetype, filetype, user, permalink, timestamp, etc.)\n- data.response_metadata.next_cursor: use for pagination

$io.slack.listScheduledMessages ​

List scheduled messages in a Slack channel (or across all channels).

Usage ​

js
$io.slack.listScheduledMessages(connectionName, channel, limit, cursor, customRequestProperties)

Description ​

Wraps Slack's chat.scheduledMessages.list to retrieve pending scheduled messages. Can return messages across all channels accessible to the token, or filtered to a single channel.

Token & scope notes

  • Requires chat:write (and chat:write.public if posting into public channels where the bot is not a member).

Pagination

  • Slack responses include response_metadata.next_cursor.
  • Pass that cursor back to fetch additional pages.

Common flow

  • Call after scheduleMessage to confirm scheduling.
  • Iterate pages until no next_cursor remains.

Usage Scenarios ​

  • View pending scheduled posts for a specific channel
  • Audit all scheduled messages across a workspace
  • Implement a scheduler UI to manage upcoming messages

Parameters ​

connectionName ​

Type: String

Required: Yes

Slack connection name configured in ioLoop.

channel ​

Type: String

Required: No

Channel ID to filter by (e.g., 'C0123456789'). Leave null to list across all channels.

limit ​

Type: Number

Required: No

Maximum number of results per page. Slack may cap this internally.

cursor ​

Type: String

Required: No

Pagination cursor from a previous call.

customRequestProperties ​

Type: Object

Required: No

Advanced request options (pass-through). In BYO mode, may include { actor: 'bot' | 'user' }.

Output ​

Type: Promise

Resolves to { status: true, data, err }. On success (Slack response):\n- data.ok: true\n- data.scheduled_messages: Array of { id, channel_id, post_at, text, ... }\n- data.response_metadata.next_cursor: for pagination

$io.slack.listUsers ​

List workspace users with basic profile details; supports pagination.

Usage ​

js
$io.slack.listUsers(connectionName, limit, cursor, customRequestProperties)

Description ​

Wraps Slack's users.list to retrieve members of the workspace. Returns an array of members with user objects (id, name, profile, deleted, is_bot, etc.).

Token & scope notes

  • Requires users:read.
  • To include email addresses in profile.email, the token must have users:read.email.

Pagination

  • Slack responses include response_metadata.next_cursor.
  • Pass that cursor back via the cursor parameter to fetch the next page.

Usage Scenarios ​

  • Build a people picker or mention autocomplete
  • Audit active vs. deactivated users
  • Fetch user IDs and emails for mapping to internal accounts

Parameters ​

connectionName ​

Type: String

Required: Yes

Slack connection name configured in ioLoop.

limit ​

Type: Number

Required: No

Max users per page (Slack caps apply).

cursor ​

Type: String

Required: No

Pagination cursor from a previous call's response_metadata.next_cursor.

customRequestProperties ​

Type: Object

Required: No

Advanced request options (pass-through). In BYO mode, may include { actor: 'bot' | 'user' }.

Output ​

Type: Promise

Resolves to { status: true, data, err }. On success (Slack response):\n- data.ok: true\n- data.members: Array of user objects (id, name, profile.real_name, profile.email, etc.)\n- data.response_metadata.next_cursor: for pagination

$io.slack.postEphemeral ​

Send an ephemeral (private, non-persistent) message to a specific user in a Slack channel.

Usage ​

js
$io.slack.postEphemeral(connectionName, channel, user, text, blocks, attachments, customRequestProperties)

Description ​

Wraps Slack's chat.postEphemeral to send a temporary, private message in a channel, visible only to the specified user. Ephemeral messages do not persist in history and disappear when the user refreshes Slack or leaves the channel.

Token & scope notes

  • Requires chat:write.
  • Ephemeral messages cannot be sent into public channels the app is not a member of; chat:write.public is not applicable.
  • In BYO (Custom OAuth2) profiles, you may use a user token by passing { actor: 'user' } via customRequestProperties.

Limitations

  • Ephemeral messages are always standalone (no thread_ts, reply_broadcast, or metadata).
  • The user must be present in the target channel context.

Usage Scenarios ​

  • Provide just-in-time tips or onboarding hints only visible to a single user
  • Send contextual error or validation feedback without cluttering the channel
  • Notify a user about something privately while keeping the conversation clean

Parameters ​

connectionName ​

Type: String

Required: Yes

Slack connection name configured in ioLoop.

channel ​

Type: String

Required: Yes

Channel ID (e.g., 'C0123456789'). Must be a channel where the app is a member.

user ​

Type: String

Required: Yes

Slack user ID (e.g., 'U12345678') of the user who should see the ephemeral message.

text ​

Type: String

Required: No

Fallback and accessibility text. Recommended even when blocks are provided.

blocks ​

Type: Array | Object

Required: No

Block Kit payload for rich message layout.

attachments ​

Type: Array

Required: No

Legacy message attachments (not recommended; prefer blocks).

customRequestProperties ​

Type: Object

Required: No

Advanced request options (pass-through). In BYO mode, may include { actor: 'bot' | 'user' }.

Output ​

Type: Promise

Resolves to { status: true, data, err }. On success (Slack response):

  • data.ok: true
  • data.message_ts: ephemeral message timestamp (ephemeral only; not persisted)
  • data.channel: channel ID
  • data.user: user ID the message was shown to

$io.slack.postMessage ​

Post a message to a Slack conversation (channel, group, IM) with optional Block Kit, threading, broadcast, and metadata.

Usage ​

js
$io.slack.postMessage(connectionName, channel, text, blocks, threadTs, replyBroadcast, metadata, customRequestProperties)

Description ​

Wraps Slack's chat.postMessage to send a persistent message visible to everyone in the target conversation. You can include plain text, Block Kit blocks, post as a threaded reply via threadTs, optionally broadcast a threaded reply to the channel with replyBroadcast, and attach metadata ({ event_type, event_payload }) for app-defined context.

Token & scope notes

  • Requires chat:write.
  • To post to public channels the bot is not a member of, the token also needs chat:write.public.
  • In BYO (Custom OAuth2) profiles, you may use a user token by passing { actor: 'user' } via customRequestProperties (ensure user token has appropriate permissions).

Threading

  • Provide the root thread timestamp as threadTs (Slack ts). If a Date/moment is supplied, it is converted to epoch seconds; Slack ts strings pass through unchanged.
  • Set replyBroadcast=true to also broadcast a thread reply back into the channel timeline.

Usage Scenarios ​

  • Send announcements or alerts into a channel
  • Post a threaded reply to keep discussions organized
  • Attach Block Kit content and custom app metadata for downstream processing

Parameters ​

connectionName ​

Type: String

Required: Yes

Slack connection name configured in ioLoop.

channel ​

Type: String

Required: Yes

Conversation ID (e.g., 'C0123456789').

text ​

Type: String

Required: No

Fallback and accessibility text. Recommended even when blocks is provided.

blocks ​

Type: Array | Object

Required: No

Block Kit payload describing rich message layout. Optional if text adequately represents content.

threadTs ​

Type: String | Date | moment

Required: No

Root thread timestamp (Slack ts). If Date/moment is provided, it is normalized to epoch seconds. Slack ts strings are passed through as-is.

replyBroadcast ​

Type: Boolean

Required: No

If true and posting in a thread, also broadcast the reply to the channel.

metadata ​

Type: Object

Required: No

Optional app metadata: { event_type: String (<=32 chars), event_payload: Object (<= ~8 KB JSON) }.

customRequestProperties ​

Type: Object

Required: No

Advanced request options (pass-through). In BYO mode, may include { actor: 'bot' | 'user' } to select bearer.

Output ​

Type: Promise

Resolves to { status: true, data, err }. On success (Slack response):

  • data.ok: true
  • data.channel: channel ID
  • data.ts: message timestamp (Slack ts)
  • data.message: full message object (text, blocks, user/bot info, thread_ts when applicable)

$io.slack.processInboundWebhook ​

Verify, parse, and optionally enrich incoming Slack webhooks (Events API, Interactivity, Slash Commands).

Usage ​

js
$io.slack.processInboundWebhook(connectionName, headers, rawBody, opts)

Description ​

This helper verifies the Slack signature, parses the inbound body (JSON or application/x-www-form-urlencoded), handles URL verification, and returns a normalized envelope plus a convenient info object. When opts.enrich is `true), it will optionally enrich the info with user, channel, and (for IMs) the DM counterparty via Web API calls.

What it handles

  • Events API: type: "event_callback" (e.g., message, app_mention). It surfaces channelId, userId, text, ts, threadTs, channelType, mentions, files, etc.
  • Interactivity: Button/select/modals POSTs with form-encoded payload JSON.
  • Slash Commands: Form-encoded submissions including command, text, channel_id, user_id.

Signature verification

  • Uses Slack's signing secret and v0 HMAC scheme. Requires the exact raw request body bytes (not parsed JSON) and the request headers x-slack-signature and x-slack-request-timestamp.
  • Rejects stale requests older than opts.maxAgeSec (default 300s).

URL verification

  • If the payload is {"type":"url_verification","challenge": "..."}, the function returns isChallenge: true and the challenge value so you can echo it back to Slack.

Normalized output

  • envelope: canonicalized view of the payload:
    • Events API → { type: "event_callback", event, payload, raw }
    • Interactivity → { type: "<interaction_type>", event:null, payload, raw }
    • Slash Command → handled as form; details surfaced on info
  • info: quick-access fields commonly needed by handlers:
    • source: "event_callback" | "interaction" | "slash_command" | "json" | "form"
    • teamId
    • channelId
    • channelType (when available or after enrichment): "channel" | "group" | "im" | "mpim"
    • userId, botId
    • text, blocks
    • ts, threadTs
    • files (if present on message events)
    • mentions: extracted <@U...> mentions from text
    • Slash: command, commandText, responseUrl

Optional enrichment (if opts.enrich === true)

  • enrichment.user: result of users.info for info.userId
  • enrichment.channel: result of conversations.info for info.channelId
  • enrichment.dmOtherUserId and enrichment.dmOtherUser for IMs, via conversations.members (+ optional users.info)

Typical handler flow

  1. Call this function with headers, raw body, and opts.signingSecret.
  2. If data.isChallenge → respond with data.challenge (plain text).
  3. Else, inspect data.info + data.envelope to decide how to respond.
  4. Optionally use $io.slack.postMessage / postEphemeral / etc.

Example

js
const headers = $io.system.getWebHookRequestHeaders(hookCtxt);
const rawBody = hookCtxt.znCfCtxt.reqInfo.rawBody;

const p = await $io.slack.processInboundWebhook('my-slack-conn', headers, rawBody, {
    signingSecret: process.env.SLACK_SIGNING_SECRET,
    maxAgeSec: 300,
    enrich: true,
    enrichFlags: { user: true, channel: true, dmCounterpart: true }
});

if(!p.status) {
    // 401 Unauthorized
}

if(p.data.isChallenge) {
    // 200 text/plain with p.data.challenge
}

const { info, enrichment, envelope } = p.data;

if(envelope.type === 'event_callback' && envelope.event?.type === 'app_mention') {
    await $io.slack.postMessage('my-slack-conn', info.channelId, `Hi <@${info.userId}>`, null, info.threadTs || info.ts);
}

Usage Scenarios ​

  • Receive Slack Events API messages and route by channel/user/thread
  • Handle Interactivity payloads (buttons, selects, modals) with verified requests
  • Process Slash Commands and respond in-channel or ephemerally
  • Normalize diverse Slack webhook bodies into a single shape for business logic

Parameters ​

connectionName ​

Type: String

Required: Yes

Name of the Slack connection configured in the ioLoop app.

headers ​

Type: Object

Required: Yes

Raw HTTP headers from the inbound webhook request. Must include Slack's 'x-slack-signature' and 'x-slack-request-timestamp'.

rawBody ​

Type: Buffer | String

Required: Yes

The exact raw request body as received (BEFORE parsing). Required for HMAC verification.

opts ​

Type: Object

Required: No

Options for verification and enrichment.

Output ​

Type: Promise

Resolves to an object: \n- status: true on success\n- data: {\n verified: true,\n isChallenge: boolean,\n challenge?: string,\n source: string,\n envelope: { type?: string, event?: Object, payload?: Object, raw: any },\n info: { source: string, teamId?: string, channelId?: string, channelType?: string, userId?: string, botId?: string, text?: string, blocks?: any, ts?: string, threadTs?: string, files?: any, mentions: string[], command?: string, commandText?: string, responseUrl?: string },\n enrichment?: { user?: Object, channel?: Object, dmOtherUserId?: string, dmOtherUser?: Object, err?: string }\n}\n- err: Error message if failed (e.g., signature verification failure, stale timestamp).

$io.slack.scheduleMessage ​

Schedule a Slack message to be posted at a future time.

Usage ​

js
$io.slack.scheduleMessage(connectionName, channel, text, postAt, blocks, threadTs, customRequestProperties)

Description ​

Wraps Slack's chat.scheduleMessage to queue a message for delivery at a future time. Provide a channel, the message content (text and/or blocks), and a postAt time in the future.

Token & scope notes

  • Requires chat:write.
  • To schedule messages into public channels where the bot is not a member, the token also needs chat:write.public.

Scheduling rules

  • postAt must be a time in the future, expressed as Unix epoch seconds.
  • Dates or moment objects are normalized automatically.
  • Optional: supply a threadTs to schedule a threaded reply instead of a new top-level message.

Limitations

  • Maximum scheduled time is ~120 days in the future.
  • Scheduled messages count toward per-workspace limits.

Usage Scenarios ​

  • Schedule an announcement for later in the day
  • Queue reminders or notifications without running a separate job
  • Post a threaded reply at a specific time

Parameters ​

connectionName ​

Type: String

Required: Yes

Slack connection name configured in ioLoop.

channel ​

Type: String

Required: Yes

Channel ID (e.g., 'C0123456789').

text ​

Type: String

Required: No

Fallback and accessibility text. Recommended even when blocks are provided.

postAt ​

Type: String | Number | Date | moment

Required: Yes

When to post. Slack expects Unix epoch seconds, but Date or moment objects are also accepted.

blocks ​

Type: Array | Object

Required: No

Block Kit payload describing rich message layout.

threadTs ​

Type: String | Date | moment

Required: No

Root thread timestamp (Slack ts). If provided, schedules the message as a thread reply.

customRequestProperties ​

Type: Object

Required: No

Advanced request options (pass-through). In BYO mode, may include { actor: 'bot' | 'user' }.

Output ​

Type: Promise

Resolves to { status: true, data, err }. On success (Slack response):\n- data.ok: true\n- data.channel: channel ID\n- data.scheduled_message_id: ID of the scheduled message\n- data.post_at: Unix epoch seconds when the message will post