Slack Integration
Receive webhooks from Slack for events, slash commands, and interactive components.
Setup
1. Create a Source in Hookbase
curl -X POST https://api.hookbase.app/api/sources \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Slack App",
"slug": "slack",
"provider": "slack",
"signingSecret": "your-signing-secret"
}'Save your webhook URL:
https://api.hookbase.app/ingest/{orgSlug}/slack2. Create a Slack App
- Go to Slack API
- Click Create New App
- Choose From scratch
- Name your app and select a workspace
- Click Create App
3. Configure Event Subscriptions
- In your Slack app settings, go to Event Subscriptions
- Toggle Enable Events to On
- Enter your Hookbase URL as the Request URL
- Slack sends a
url_verificationchallenge; Hookbase answers it — see URL Verification - Under Subscribe to bot events, add events like:
message.channelsmessage.imapp_mentionreaction_added
- Click Save Changes
4. Get Your Signing Secret
- Go to Basic Information in your Slack app
- Under App Credentials, find Signing Secret
- Copy this secret to your Hookbase source configuration
5. Install the App
- Go to Install App in your Slack app settings
- Click Install to Workspace
- Authorize the requested permissions
Signature Verification
Slack signs requests using HMAC-SHA256. The signature is sent in headers:
X-Slack-Request-Timestamp: 1531420618
X-Slack-Signature: v0=a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503Hookbase verifies this automatically:
{
"provider": "slack",
"signingSecret": "your-signing-secret"
}Timestamp Tolerance
Slack signatures include a timestamp. Hookbase rejects requests older than 5 minutes to prevent replay attacks.
Event Types
Message Events
{
"token": "...",
"team_id": "T1234567",
"event": {
"type": "message",
"channel": "C1234567",
"user": "U1234567",
"text": "Hello world",
"ts": "1234567890.123456"
},
"type": "event_callback",
"event_id": "Ev1234567",
"event_time": 1234567890
}App Mention
{
"token": "...",
"team_id": "T1234567",
"event": {
"type": "app_mention",
"user": "U1234567",
"text": "<@U0LAN0Z89> help",
"channel": "C1234567",
"ts": "1234567890.123456"
},
"type": "event_callback"
}Reaction Added
{
"token": "...",
"team_id": "T1234567",
"event": {
"type": "reaction_added",
"user": "U1234567",
"reaction": "thumbsup",
"item": {
"type": "message",
"channel": "C1234567",
"ts": "1234567890.123456"
}
},
"type": "event_callback"
}Slash Commands
Setup
- Go to Slash Commands in your Slack app
- Click Create New Command
- Enter your command (e.g.,
/mycommand) - Set Request URL to your Hookbase URL
- Click Save
Payload Format
{
"command": "/mycommand",
"text": "argument text",
"user_id": "U1234567",
"user_name": "username",
"channel_id": "C1234567",
"channel_name": "general",
"team_id": "T1234567",
"response_url": "https://hooks.slack.com/commands/..."
}Responding to Commands
Your destination should respond within 3 seconds with:
{
"response_type": "in_channel",
"text": "Command received!"
}Or use the response_url for delayed responses.
Interactive Components
Setup
- Go to Interactivity & Shortcuts
- Toggle Interactivity to On
- Set Request URL to your Hookbase URL
- Click Save Changes
Button Click Payload
{
"type": "block_actions",
"user": {
"id": "U1234567",
"username": "user"
},
"actions": [{
"action_id": "button_click",
"block_id": "block_1",
"type": "button",
"value": "click_me_123"
}],
"response_url": "https://hooks.slack.com/actions/...",
"trigger_id": "1234567890.123456"
}Transform Examples
Warning
A transform cannot skip a delivery. return null does not suppress anything — the engine
serializes it, so your destination receives the four-byte body null with Content-Type: text/plain. Most endpoints answer that with a 400, which Hookbase records as a failed delivery
and then retries on the route's schedule.
Skipping is a route filter, evaluated on the event before any transform runs: a route whose filter does not pass creates no delivery at all. Put the condition there and let the transform assume it.
Log to Database
function transform(payload) {
if (payload.type !== 'event_callback') {
return payload;
}
const event = payload.event;
return {
event_type: event.type,
team_id: payload.team_id,
channel_id: event.channel,
user_id: event.user,
text: event.text,
timestamp: event.ts,
raw_event: payload
};
}Forward to Discord
Put this route behind a filter so only channel messages reach it — both fields are plain paths, so the condition is expressible and the transform does not have to defend against anything:
{
"name": "Channel messages only",
"logic": "AND",
"conditions": [
{ "field": "type", "operator": "equals", "value": "event_callback" },
{ "field": "event.type", "operator": "equals", "value": "message" }
]
}function transform(payload) {
const event = payload.event;
return {
content: event.text,
username: `Slack (${event.user})`,
embeds: [{
color: 0x4A154B,
footer: {
text: `From Slack #${event.channel}`
},
timestamp: new Date(parseFloat(event.ts) * 1000).toISOString()
}]
};
}Extract Mentions
function transform(payload) {
if (payload.event?.type !== 'app_mention') {
return payload;
}
const event = payload.event;
const text = event.text.replace(/<@[A-Z0-9]+>\s*/g, '').trim();
return {
action: 'mention',
user: event.user,
channel: event.channel,
command: text,
timestamp: event.ts
};
}Filter Examples
Only Message Events
{
"name": "Messages Only",
"logic": "AND",
"conditions": [
{
"field": "event.type",
"operator": "equals",
"value": "message"
}
]
}Exclude Bot Messages
{
"name": "Non-Bot Messages",
"logic": "AND",
"conditions": [
{
"field": "event.bot_id",
"operator": "not_exists",
"value": ""
}
]
}Specific Channel
{
"name": "Alerts Channel",
"logic": "AND",
"conditions": [
{
"field": "event.channel",
"operator": "equals",
"value": "C1234567"
}
]
}App Mentions Only
{
"name": "App Mentions",
"logic": "AND",
"conditions": [
{
"field": "event.type",
"operator": "equals",
"value": "app_mention"
}
]
}URL Verification
When you first set up Event Subscriptions, Slack sends a verification challenge:
{
"type": "url_verification",
"challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P",
"token": "..."
}Hookbase answers it with {"challenge": "<the same value>"}, which is what Slack requires before
it will save the Request URL. You do not need a handler of your own, and nothing reaches your
destinations for it.
Two things to expect:
- The handshake is not stored as an event. It has no event type and nothing worth routing, so looking for it in the dashboard after Slack accepts the URL will turn up nothing. Slack's own "Verified ✓" is the confirmation.
rejectInvalidSignaturesapplies to it. With that on and a wrongsigningSecretstored, Hookbase answers401and Slack reports that your URL did not respond. Leave the secret unset while you save the URL, then paste it in — or set the correct one first. Slack signs the handshake exactly like an event, so a correct secret verifies it.
Slack re-sends the handshake every time you change the Request URL, not only the first time.
Best Practices
-
Respond quickly: Slack expects responses within 3 seconds for slash commands and interactions
-
Use response_url: For longer operations, acknowledge immediately and use
response_urlfor the actual response -
Handle retries: Slack may retry failed requests; check for
X-Slack-Retry-Numheader -
Filter bot messages: Exclude bot messages to prevent loops
-
Verify the signature: Always use signature verification in production
Troubleshooting
URL Verification Failed
- Confirm the source's
providerisslack. Hookbase answers the handshake only for a source declared as Slack — on any other provider the request is stored as an ordinary event and Slack gets the ingest response instead of its challenge, which it rejects - If
rejectInvalidSignaturesis on, confirm the storedsigningSecretis right. A wrong secret makes Hookbase answer401and Slack reports the URL as unresponsive, which points at the network rather than at the secret - Check the URL is reachable from the internet and has no extra characters or spaces
Events Not Arriving
- Check Event Subscriptions is enabled
- Verify the correct events are subscribed
- Ensure the app is installed to the workspace
- Check the app has required OAuth scopes
Signature Verification Failed
- Use the Signing Secret, not the Verification Token
- Verify the secret is copied correctly (no extra spaces)
- Check if the request timestamp is too old (>5 minutes)