LoopFour

Slack Triggers

Trigger workflows from Slack events, commands, and interactions

Slack Triggers

Slack triggers start workflows in response to Slack events, slash commands, and interactive components like button clicks and modal submissions.

Overview

Three types of Slack triggers are supported:

Trigger TypeUse CaseExample
Event TriggerRespond to messages, mentions, reactionsBot @mentioned, keyword detected
Slash CommandUser runs /command/invoice create, /approve
InteractiveButton clicks, modal submissionsApprove button, form submission

Prerequisites

Environment Configuration

# Required for signature verification
SLACK_SIGNING_SECRET=your_signing_secret

Slack App Setup

  1. Create a Slack App at api.slack.com/apps
  2. Enable Event Subscriptions and point to your endpoint
  3. Add required bot scopes
  4. Install app to workspace

Endpoint URLs

Configure these URLs in your Slack app settings:

FeatureURL
Event Subscriptionshttps://your-domain.com/api/v1/slack/events
Slash Commandshttps://your-domain.com/api/v1/slack/commands
Interactivityhttps://your-domain.com/api/v1/slack/interactive

Event Triggers

Respond to Slack Events API events like messages, mentions, and reactions.

Configuration Schema

{
  "trigger": {
    "type": "slack_event",
    "eventType": "message",
    "channelTypes": ["channel", "im"],
    "keywords": ["invoice", "billing"],
    "regex": "invoice\\s+#?\\d+",
    "userIds": ["U123456"],
    "filter": {
      "event.channel_type": { "$eq": "channel" }
    }
  }
}

Configuration Fields

FieldTypeRequiredDescription
typestringYesMust be slack_event
eventTypestringYesSlack event type or * for all
channelTypesarrayNoFilter by channel type (channel, group, im, mpim)
keywordsarrayNoTrigger only if message contains keyword (case-insensitive)
regexstringNoTrigger only if message matches regex
userIdsarrayNoTrigger only for specific user IDs
filterobjectNoAdvanced filter using dot notation and operators

Supported Event Types

Event TypeDescriptionKey Fields
messageChannel or DM messagetext, user, channel, ts
app_mentionBot @mentioned in messagetext, user, channel, ts
reaction_addedEmoji reaction addedreaction, user, item.channel, item.ts
reaction_removedEmoji reaction removedreaction, user, item.channel, item.ts
channel_createdNew channel createdchannel.id, channel.name, channel.creator
channel_deletedChannel deletedchannel
member_joined_channelUser joined channeluser, channel, inviter
member_left_channelUser left channeluser, channel
file_sharedFile uploadedfile_id, user_id, channel_id
app_home_openedUser opened Home tabuser, tab

Event Payload Structure

{
  "input": {
    "slackEvent": {
      "id": "evt_abc123",
      "source": "events_api",
      "payload": {
        "type": "event_callback",
        "team_id": "T123456",
        "api_app_id": "A123456",
        "event": {
          "type": "message",
          "user": "U123456",
          "channel": "C789012",
          "channel_type": "channel",
          "text": "Create invoice for Acme Corp",
          "ts": "1234567890.123456",
          "thread_ts": null
        },
        "event_id": "Ev123456",
        "event_time": 1234567890
      }
    }
  }
}

Example: Keyword-Based Message Trigger

{
  "name": "Invoice Request Handler",
  "trigger": {
    "type": "slack_event",
    "eventType": "message",
    "channelTypes": ["channel"],
    "keywords": ["invoice", "billing", "payment"]
  },
  "steps": [
    {
      "id": "parse-request",
      "type": "transform",
      "config": {
        "operation": "map",
        "data": {
          "user": "{{input.slackEvent.payload.event.user}}",
          "channel": "{{input.slackEvent.payload.event.channel}}",
          "text": "{{input.slackEvent.payload.event.text}}"
        }
      }
    },
    {
      "id": "notify-finance",
      "type": "slack",
      "config": {
        "action": "sendMessage",
        "channel": "#finance-requests",
        "text": "New invoice request from <@{{steps.parse-request.output.user}}>:\n> {{steps.parse-request.output.text}}"
      }
    }
  ]
}

Example: Bot Mention Trigger

{
  "name": "Bot Command Handler",
  "trigger": {
    "type": "slack_event",
    "eventType": "app_mention"
  },
  "steps": [
    {
      "id": "process-mention",
      "type": "action",
      "action": "ai.analyze",
      "config": {
        "prompt": "Parse this request and determine the intent: {{input.slackEvent.payload.event.text}}"
      }
    },
    {
      "id": "respond",
      "type": "slack",
      "config": {
        "action": "sendMessage",
        "channel": "{{input.slackEvent.payload.event.channel}}",
        "threadTs": "{{input.slackEvent.payload.event.ts}}",
        "text": "{{steps.process-mention.output.response}}"
      }
    }
  ]
}

Example: Reaction Trigger

{
  "name": "Approval via Reaction",
  "trigger": {
    "type": "slack_event",
    "eventType": "reaction_added",
    "filter": {
      "event.reaction": { "$in": ["white_check_mark", "heavy_check_mark"] }
    }
  },
  "steps": [
    {
      "id": "log-approval",
      "type": "action",
      "action": "database.insert",
      "config": {
        "table": "approvals",
        "data": {
          "approved_by": "{{input.slackEvent.payload.event.user}}",
          "message_ts": "{{input.slackEvent.payload.event.item.ts}}",
          "channel": "{{input.slackEvent.payload.event.item.channel}}"
        }
      }
    }
  ]
}

Slash Command Triggers

Respond to custom slash commands like /invoice, /approve, etc.

Configuration Schema

{
  "trigger": {
    "type": "slack_slash_command",
    "command": "/invoice"
  }
}

Configuration Fields

FieldTypeRequiredDescription
typestringYesMust be slack_slash_command
commandstringYesThe slash command (including /)

Command Payload Structure

{
  "input": {
    "slackEvent": {
      "id": "evt_cmd123",
      "source": "slash_command",
      "payload": {
        "token": "xxx",
        "team_id": "T123456",
        "team_domain": "myworkspace",
        "channel_id": "C789012",
        "channel_name": "general",
        "user_id": "U123456",
        "user_name": "johndoe",
        "command": "/invoice",
        "text": "create for Acme Corp $5000",
        "response_url": "https://hooks.slack.com/commands/xxx",
        "trigger_id": "123.456.xxx",
        "api_app_id": "A123456"
      }
    }
  }
}

Key Payload Fields

FieldDescription
commandThe slash command (e.g., /invoice)
textText after the command
user_idUser who ran the command
user_nameUsername
channel_idChannel where command was run
trigger_idUsed to open modals (expires in 3 seconds)
response_urlURL to send delayed responses

Example: Invoice Creation Command

{
  "name": "Create Invoice Command",
  "trigger": {
    "type": "slack_slash_command",
    "command": "/invoice"
  },
  "steps": [
    {
      "id": "parse-command",
      "type": "transform",
      "config": {
        "operation": "map",
        "data": {
          "args": "{{input.slackEvent.payload.text | split: ' '}}",
          "user": "{{input.slackEvent.payload.user_id}}",
          "channel": "{{input.slackEvent.payload.channel_id}}"
        }
      }
    },
    {
      "id": "open-modal",
      "type": "slack",
      "config": {
        "action": "openModal",
        "triggerId": "{{input.slackEvent.payload.trigger_id}}",
        "view": {
          "type": "modal",
          "callback_id": "create_invoice_modal",
          "title": { "type": "plain_text", "text": "Create Invoice" },
          "submit": { "type": "plain_text", "text": "Create" },
          "blocks": [
            {
              "type": "input",
              "block_id": "customer",
              "element": {
                "type": "plain_text_input",
                "action_id": "customer_input",
                "initial_value": "{{steps.parse-command.output.args[0]}}"
              },
              "label": { "type": "plain_text", "text": "Customer" }
            },
            {
              "type": "input",
              "block_id": "amount",
              "element": {
                "type": "plain_text_input",
                "action_id": "amount_input"
              },
              "label": { "type": "plain_text", "text": "Amount" }
            }
          ]
        }
      }
    }
  ]
}

Example: Help Command

{
  "name": "Help Command",
  "trigger": {
    "type": "slack_slash_command",
    "command": "/workflow-help"
  },
  "steps": [
    {
      "id": "send-help",
      "type": "action",
      "action": "http.post",
      "config": {
        "url": "{{input.slackEvent.payload.response_url}}",
        "body": {
          "response_type": "ephemeral",
          "blocks": [
            {
              "type": "section",
              "text": {
                "type": "mrkdwn",
                "text": "*Available Commands:*\n• `/invoice create` - Create a new invoice\n• `/invoice list` - List recent invoices\n• `/approve` - View pending approvals"
              }
            }
          ]
        }
      }
    }
  ]
}

Interactive Triggers

Respond to button clicks, menu selections, modal submissions, and shortcuts.

Configuration Schema

{
  "trigger": {
    "type": "slack_interactive",
    "interactionType": "block_actions",
    "actionId": "approve_button",
    "callbackId": "approval_modal",
    "filter": {
      "actions[0].value": { "$exists": true }
    }
  }
}

Configuration Fields

FieldTypeRequiredDescription
typestringYesMust be slack_interactive
interactionTypestringYesInteraction type or * for all
actionIdstringNoFilter by action_id (for block_actions)
callbackIdstringNoFilter by callback_id (for modals/shortcuts)
filterobjectNoAdvanced filter

Interaction Types

TypeDescriptionWhen Triggered
block_actionsButton click, menu selectionUser clicks button or selects option
view_submissionModal form submittedUser submits modal
view_closedModal closedUser closes modal without submitting
shortcutGlobal shortcutUser runs global shortcut
message_actionMessage shortcutUser runs message shortcut

Interactive Payload Structure

Block Actions (Button Click):

{
  "input": {
    "slackEvent": {
      "id": "evt_int123",
      "source": "interactive",
      "payload": {
        "type": "block_actions",
        "team": { "id": "T123456", "domain": "myworkspace" },
        "user": { "id": "U123456", "name": "johndoe" },
        "channel": { "id": "C789012", "name": "general" },
        "message": { "ts": "1234567890.123456" },
        "trigger_id": "123.456.xxx",
        "response_url": "https://hooks.slack.com/actions/xxx",
        "actions": [
          {
            "action_id": "approve_invoice",
            "block_id": "approval_block",
            "type": "button",
            "value": "inv_12345"
          }
        ]
      }
    }
  }
}

View Submission (Modal):

{
  "input": {
    "slackEvent": {
      "id": "evt_modal123",
      "source": "interactive",
      "payload": {
        "type": "view_submission",
        "team": { "id": "T123456" },
        "user": { "id": "U123456", "name": "johndoe" },
        "trigger_id": "123.456.xxx",
        "view": {
          "id": "V123456",
          "callback_id": "create_invoice_modal",
          "private_metadata": "{\"channel\":\"C789012\"}",
          "state": {
            "values": {
              "customer": {
                "customer_input": { "value": "Acme Corp" }
              },
              "amount": {
                "amount_input": { "value": "5000" }
              }
            }
          }
        }
      }
    }
  }
}

Example: Button Click Handler

{
  "name": "Handle Approval Button",
  "trigger": {
    "type": "slack_interactive",
    "interactionType": "block_actions",
    "actionId": "approve_invoice"
  },
  "steps": [
    {
      "id": "get-invoice",
      "type": "action",
      "action": "quickbooks.getInvoice",
      "config": {
        "invoiceId": "{{input.slackEvent.payload.actions[0].value}}"
      }
    },
    {
      "id": "approve",
      "type": "action",
      "action": "quickbooks.updateInvoice",
      "config": {
        "invoiceId": "{{input.slackEvent.payload.actions[0].value}}",
        "status": "approved"
      }
    },
    {
      "id": "notify",
      "type": "action",
      "action": "http.post",
      "config": {
        "url": "{{input.slackEvent.payload.response_url}}",
        "body": {
          "replace_original": true,
          "text": ":white_check_mark: Invoice approved by <@{{input.slackEvent.payload.user.id}}>"
        }
      }
    }
  ]
}

Example: Modal Submission Handler

{
  "name": "Process Invoice Modal",
  "trigger": {
    "type": "slack_interactive",
    "interactionType": "view_submission",
    "callbackId": "create_invoice_modal"
  },
  "steps": [
    {
      "id": "extract-values",
      "type": "transform",
      "config": {
        "operation": "map",
        "data": {
          "customer": "{{input.slackEvent.payload.view.state.values.customer.customer_input.value}}",
          "amount": "{{input.slackEvent.payload.view.state.values.amount.amount_input.value}}",
          "metadata": "{{input.slackEvent.payload.view.private_metadata | parseJson}}"
        }
      }
    },
    {
      "id": "create-invoice",
      "type": "action",
      "action": "quickbooks.createInvoice",
      "config": {
        "customerName": "{{steps.extract-values.output.customer}}",
        "amount": "{{steps.extract-values.output.amount}}"
      }
    },
    {
      "id": "confirm",
      "type": "slack",
      "config": {
        "action": "sendMessage",
        "channel": "{{steps.extract-values.output.metadata.channel}}",
        "text": ":white_check_mark: Invoice created for {{steps.extract-values.output.customer}} - ${{steps.extract-values.output.amount}}"
      }
    }
  ]
}

Built-in Approval Handling

The platform includes built-in support for human-in-the-loop approvals via Slack. When a workflow step uses the approval type with Slack notifications, approval buttons are automatically handled.

Automatic Approval Button Handling

Buttons with action IDs matching approval_approve_<token> or approval_reject_<token> are automatically processed:

  1. The approval decision is recorded
  2. The waiting workflow is resumed
  3. The Slack message is updated with the decision

No custom trigger configuration is needed for approval workflows.

Important Behavior

3-Second Acknowledgment Rule

Slack requires responses within 3 seconds. All triggers:

  • Acknowledge immediately with a brief response
  • Process the event asynchronously
  • Use response_url for delayed responses
// Immediate acknowledgment (automatic)
{ "ok": true }
 
// Delayed response via response_url
{
  "replace_original": true,
  "text": "Processing complete!"
}

URL Verification

When setting up Event Subscriptions, Slack sends a verification challenge. This is handled automatically:

// Slack sends:
{ "type": "url_verification", "challenge": "abc123" }
 
// Endpoint responds:
{ "challenge": "abc123" }

Retry Deduplication

Slack retries failed requests with headers:

  • X-Slack-Retry-Num: Retry attempt number
  • X-Slack-Retry-Reason: Reason for retry

Retries are automatically deduplicated using Redis with a 1-hour TTL.

Signature Verification

All requests are verified using HMAC-SHA256:

  1. Compute: v0:timestamp:raw_body
  2. Sign with SLACK_SIGNING_SECRET
  3. Compare with X-Slack-Signature header
  4. Reject if timestamp is >5 minutes old (replay attack prevention)

Filter Operators

Use these operators in the filter configuration:

OperatorDescriptionExample
$eqEqual to{ "event.type": { "$eq": "message" } }
$neNot equal to{ "event.channel_type": { "$ne": "im" } }
$inIn array{ "event.reaction": { "$in": ["thumbsup", "heart"] } }
$existsField exists{ "event.thread_ts": { "$exists": true } }

Access nested fields with dot notation: event.item.channel

Troubleshooting

Common Issues

IssueCauseSolution
Signature verification failedWrong secret or clock skewVerify SLACK_SIGNING_SECRET, check server time
Events not receivedWrong URL or app not installedCheck Event Subscriptions URL, reinstall app
Modal won't openExpired trigger_idOpen modal within 3 seconds of receiving event
Duplicate eventsRetries not deduplicatedCheck Redis connection
Missing scopesInsufficient permissionsAdd required scopes in app settings

Required Scopes by Feature

FeatureRequired Scopes
Receive messageschannels:history, groups:history, im:history, mpim:history
Receive mentionsapp_mentions:read
Slash commands(configured per command)
Interactive(automatic with Interactivity URL)
Post messageschat:write
Open modals(automatic with bot token)

Debug Logging

Events are logged at these points:

  1. Signature verification result
  2. Event deduplication check
  3. Matching workflow discovery
  4. Workflow trigger success/failure

Check logs for Slack event processing failed or Failed to trigger workflow from Slack event.