From 03f36599b452217bd66e81e68a42f5087b9c9165 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 20 Feb 2026 16:49:44 -0800 Subject: [PATCH 1/4] fix(trigger): handle Slack reaction_added/reaction_removed event payloads --- apps/sim/lib/webhooks/utils.server.ts | 75 +++++++++++++++++++++++++-- apps/sim/triggers/slack/webhook.ts | 14 ++++- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/apps/sim/lib/webhooks/utils.server.ts b/apps/sim/lib/webhooks/utils.server.ts index 2d749862756..b2656def953 100644 --- a/apps/sim/lib/webhooks/utils.server.ts +++ b/apps/sim/lib/webhooks/utils.server.ts @@ -679,6 +679,54 @@ async function downloadSlackFiles( return downloaded } +const SLACK_REACTION_EVENTS = new Set(['reaction_added', 'reaction_removed']) + +/** + * Fetches the text of a message from Slack using conversations.history. + * Requires the bot token to have channels:history (public) or groups:history (private) scope. + */ +async function fetchSlackMessageText( + channel: string, + messageTs: string, + botToken: string +): Promise { + try { + const params = new URLSearchParams({ + channel, + latest: messageTs, + inclusive: 'true', + limit: '1', + }) + const response = await fetch(`https://slack.com/api/conversations.history?${params}`, { + headers: { Authorization: `Bearer ${botToken}` }, + }) + + const data = (await response.json()) as { + ok: boolean + error?: string + messages?: Array<{ text?: string }> + } + + if (!data.ok) { + logger.warn('Slack conversations.history failed — message text unavailable', { + channel, + messageTs, + error: data.error, + }) + return '' + } + + return data.messages?.[0]?.text ?? '' + } catch (error) { + logger.warn('Error fetching Slack message text', { + channel, + messageTs, + error: error instanceof Error ? error.message : String(error), + }) + return '' + } +} + /** * Format webhook input based on provider */ @@ -953,6 +1001,23 @@ export async function formatWebhookInput( }) } + const eventType: string = rawEvent?.type || body?.type || 'unknown' + const isReactionEvent = SLACK_REACTION_EVENTS.has(eventType) + + // Reaction events nest channel/ts inside event.item + const channel: string = isReactionEvent + ? rawEvent?.item?.channel || '' + : rawEvent?.channel || '' + const messageTs: string = isReactionEvent + ? rawEvent?.item?.ts || '' + : rawEvent?.ts || rawEvent?.event_ts || '' + + // For reaction events, attempt to fetch the original message text + let text: string = rawEvent?.text || '' + if (isReactionEvent && channel && messageTs && botToken) { + text = await fetchSlackMessageText(channel, messageTs, botToken) + } + const rawFiles: any[] = rawEvent?.files ?? [] const hasFiles = rawFiles.length > 0 @@ -965,16 +1030,18 @@ export async function formatWebhookInput( return { event: { - event_type: rawEvent?.type || body?.type || 'unknown', - channel: rawEvent?.channel || '', + event_type: eventType, + channel, channel_name: '', user: rawEvent?.user || '', user_name: '', - text: rawEvent?.text || '', - timestamp: rawEvent?.ts || rawEvent?.event_ts || '', + text, + timestamp: messageTs, thread_ts: rawEvent?.thread_ts || '', team_id: body?.team_id || rawEvent?.team || '', event_id: body?.event_id || '', + reaction: rawEvent?.reaction || '', + item_user: rawEvent?.item_user || '', hasFiles, files, }, diff --git a/apps/sim/triggers/slack/webhook.ts b/apps/sim/triggers/slack/webhook.ts index 3d22e3be20f..7c4a5056d43 100644 --- a/apps/sim/triggers/slack/webhook.ts +++ b/apps/sim/triggers/slack/webhook.ts @@ -67,8 +67,8 @@ export const slackWebhookTrigger: TriggerConfig = { 'Go to Slack Apps page', 'If you don\'t have an app:
', 'Go to "Basic Information", find the "Signing Secret", and paste it in the field above.', - 'Go to "OAuth & Permissions" and add bot token scopes:
', - 'Go to "Event Subscriptions":
', + 'Go to "OAuth & Permissions" and add bot token scopes:
', + 'Go to "Event Subscriptions":
', 'Go to "Install App" in the left sidebar and install the app into your desired Slack workspace and channel.', 'Copy the "Bot User OAuth Token" (starts with xoxb-) and paste it in the Bot Token field above to enable file downloads.', 'Save changes in both Slack and here.', @@ -128,6 +128,16 @@ export const slackWebhookTrigger: TriggerConfig = { type: 'string', description: 'Unique event identifier', }, + reaction: { + type: 'string', + description: + 'Emoji reaction name (e.g., thumbsup). Present for reaction_added/reaction_removed events', + }, + item_user: { + type: 'string', + description: + 'User ID of the original message author. Present for reaction_added/reaction_removed events', + }, hasFiles: { type: 'boolean', description: 'Whether the message has file attachments', From 4397d8867eda009a8ca851801a45eabc529d19f1 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 20 Feb 2026 16:56:43 -0800 Subject: [PATCH 2/4] fix(trigger): use oldest param for conversations.history consistency --- apps/sim/lib/webhooks/utils.server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/lib/webhooks/utils.server.ts b/apps/sim/lib/webhooks/utils.server.ts index b2656def953..26ea3c2c170 100644 --- a/apps/sim/lib/webhooks/utils.server.ts +++ b/apps/sim/lib/webhooks/utils.server.ts @@ -693,7 +693,7 @@ async function fetchSlackMessageText( try { const params = new URLSearchParams({ channel, - latest: messageTs, + oldest: messageTs, inclusive: 'true', limit: '1', }) From c9d9b423b6bb32f8e299756085695e98df36042d Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 20 Feb 2026 16:57:53 -0800 Subject: [PATCH 3/4] fix oldest param --- apps/sim/lib/webhooks/utils.server.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/sim/lib/webhooks/utils.server.ts b/apps/sim/lib/webhooks/utils.server.ts index 26ea3c2c170..fc82ac2400b 100644 --- a/apps/sim/lib/webhooks/utils.server.ts +++ b/apps/sim/lib/webhooks/utils.server.ts @@ -694,6 +694,7 @@ async function fetchSlackMessageText( const params = new URLSearchParams({ channel, oldest: messageTs, + latest: messageTs, inclusive: 'true', limit: '1', }) From ebbc42a93ff07f73613c292e56ea2a3d76fd2cd9 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 20 Feb 2026 17:14:06 -0800 Subject: [PATCH 4/4] fix(trigger): use reactions.get API to fetch message text for thread replies --- apps/sim/lib/webhooks/utils.server.ts | 20 ++++++++++---------- apps/sim/triggers/slack/webhook.ts | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/sim/lib/webhooks/utils.server.ts b/apps/sim/lib/webhooks/utils.server.ts index fc82ac2400b..974e9552b8c 100644 --- a/apps/sim/lib/webhooks/utils.server.ts +++ b/apps/sim/lib/webhooks/utils.server.ts @@ -682,8 +682,10 @@ async function downloadSlackFiles( const SLACK_REACTION_EVENTS = new Set(['reaction_added', 'reaction_removed']) /** - * Fetches the text of a message from Slack using conversations.history. - * Requires the bot token to have channels:history (public) or groups:history (private) scope. + * Fetches the text of a reacted-to message from Slack using the reactions.get API. + * Unlike conversations.history, reactions.get works for both top-level messages and + * thread replies, since it looks up the item directly by channel + timestamp. + * Requires the bot token to have the reactions:read scope. */ async function fetchSlackMessageText( channel: string, @@ -693,23 +695,21 @@ async function fetchSlackMessageText( try { const params = new URLSearchParams({ channel, - oldest: messageTs, - latest: messageTs, - inclusive: 'true', - limit: '1', + timestamp: messageTs, }) - const response = await fetch(`https://slack.com/api/conversations.history?${params}`, { + const response = await fetch(`https://slack.com/api/reactions.get?${params}`, { headers: { Authorization: `Bearer ${botToken}` }, }) const data = (await response.json()) as { ok: boolean error?: string - messages?: Array<{ text?: string }> + type?: string + message?: { text?: string } } if (!data.ok) { - logger.warn('Slack conversations.history failed — message text unavailable', { + logger.warn('Slack reactions.get failed — message text unavailable', { channel, messageTs, error: data.error, @@ -717,7 +717,7 @@ async function fetchSlackMessageText( return '' } - return data.messages?.[0]?.text ?? '' + return data.message?.text ?? '' } catch (error) { logger.warn('Error fetching Slack message text', { channel, diff --git a/apps/sim/triggers/slack/webhook.ts b/apps/sim/triggers/slack/webhook.ts index 7c4a5056d43..3f1bbe2c0f7 100644 --- a/apps/sim/triggers/slack/webhook.ts +++ b/apps/sim/triggers/slack/webhook.ts @@ -67,7 +67,7 @@ export const slackWebhookTrigger: TriggerConfig = { 'Go to Slack Apps page', 'If you don\'t have an app:
  • Create an app from scratch
  • Give it a name and select your workspace
', 'Go to "Basic Information", find the "Signing Secret", and paste it in the field above.', - 'Go to "OAuth & Permissions" and add bot token scopes:
  • app_mentions:read - For viewing messages that tag your bot with an @
  • chat:write - To send messages to channels your bot is a part of
  • files:read - To access files and images shared in messages
  • reactions:read - For listening to emoji reactions on messages
  • channels:history - To read message text when a reaction event fires (public channels)
  • groups:history - To read message text when a reaction event fires (private channels)
', + 'Go to "OAuth & Permissions" and add bot token scopes:
  • app_mentions:read - For viewing messages that tag your bot with an @
  • chat:write - To send messages to channels your bot is a part of
  • files:read - To access files and images shared in messages
  • reactions:read - For listening to emoji reactions and fetching reacted-to message text
', 'Go to "Event Subscriptions":
  • Enable events
  • Under "Subscribe to Bot Events", add app_mention to listen to messages that mention your bot
  • For reaction events, also add reaction_added and/or reaction_removed
  • Paste the Webhook URL above into the "Request URL" field
', 'Go to "Install App" in the left sidebar and install the app into your desired Slack workspace and channel.', 'Copy the "Bot User OAuth Token" (starts with xoxb-) and paste it in the Bot Token field above to enable file downloads.',