Corsair
PluginsSlack

Slack Webhooks

All available Slack webhook events

The Slack plugin automatically handles incoming webhooks from Slack. Point your Slack app's Event Subscriptions URL to your Corsair webhook endpoint, and the plugin will automatically process events and update your database.

New to Corsair? Learn about core concepts like webhooks, hooks, and multi-tenancy before setting up webhook handlers.

Full Implementation: For the complete, up-to-date list of all webhook events and their implementations, see the Slack plugin source code on GitHub.

Setup

Configure your webhook endpoint to handle Slack events:

app/api/webhook/route.ts
import { processWebhook } from "corsair";
import { corsair } from "@/server/corsair";

export async function POST(request: Request) {
    const headers = Object.fromEntries(request.headers);
    const body = await request.json();
    
    const result = await processWebhook(corsair, headers, body);
    return result.response;
}

In your Slack app settings, set the Event Subscriptions URL to:

https://your-domain.com/api/webhook

For multi-tenancy, include the tenant ID:

https://your-domain.com/api/webhook?tenantId=tenant-123

Available Webhooks

challenge

challenge.challenge

Event Type: URL Verification Challenge

Slack sends this event when you first configure your webhook URL. Corsair automatically handles the challenge response.

When it fires: During initial webhook URL setup

Payload:

{
    type: "url_verification",
    challenge: "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P",
    token: "Jhj5dZrVaK7ZwHHjRyZWjbDl"
}

Note: You don't need to handle this event manually - Corsair responds automatically.


message

messages.message

Event Type: Message

Fires when a message is posted to a channel your app has access to.

When it fires:

  • New message posted
  • Message edited
  • Message posted in a thread

Payload Structure:

{
    type: "message",
    subtype?: "bot_message" | "me_message" | "message_changed", 
    channel: "C1234567890",
    user: "U1234567890",
    text: "Hello, world!",
    ts: "1234567890.123456",
    thread_ts?: "1234567890.123456", // Present if message is in a thread
    event_ts: "1234567890.123456",
    channel_type: "channel" | "group" | "im" | "mpim"
}

Example Usage:

corsair.ts
slack({
    webhookHooks: {
        messages: {
            message: {
                after: async (ctx, result) => {
                    // Skip bot messages
                    if (result.data.bot_id) {
                        return;
                    }
                    
                    console.log(`New message: ${result.data.text}`);
                    
                    // Process the message
                    await processMessage(result.data);
                },
            },
        },
    },
})

Key Fields:

FieldTypeDescription
typestringAlways "message"
subtypestring?Message subtype (bot_message, etc.)
channelstringChannel ID where message was posted
userstringUser ID who posted the message
textstring?Message text
tsstringMessage timestamp (unique ID)
thread_tsstring?Parent message timestamp if in thread
bot_idstring?Bot ID if posted by a bot
blocksarray?Block Kit blocks
attachmentsarray?Message attachments

created

channels.created

Event Type: Channel Created

Fires when a new channel is created.

When it fires: A new public or private channel is created

Payload Structure:

{
    type: "channel_created",
    channel: {
        id: "C1234567890",
        name: "new-channel",
        name_normalized: "new-channel",
        created: 1234567890,
        creator: "U1234567890", 
        is_channel: true,
        is_private: false,
        is_archived: false
    },
    event_ts: "1234567890.123456"
}

Example Usage:

corsair.ts
slack({
    webhookHooks: {
        channels: {
            created: {
                after: async (ctx, result) => {
                    const channel = result.data.channel;
                    console.log(`New channel created: #${channel.name}`);
                    
                    // Send welcome message
                    await ctx.api.messages.post({
                        channel: channel.id,
                        text: `Welcome to #${channel.name}!`,
                    });
                },
            },
        },
    },
})

Key Fields:

FieldTypeDescription
typestringAlways "channel_created"
channel.idstringNew channel ID
channel.namestringChannel name
channel.creatorstringUser ID of creator
channel.is_privatebooleanWhether channel is private
event_tsstringEvent timestamp

added

reactions.added

Event Type: Reaction Added

Fires when a reaction emoji is added to a message.

When it fires: A user adds an emoji reaction to a message, file, or file comment

Payload Structure:

{
    type: "reaction_added",
    user: "U1234567890",
    reaction: "thumbsup", 
    item_user: "U0987654321",
    item: {
        type: "message",
        channel: "C1234567890",
        ts: "1234567890.123456"
    },
    event_ts: "1234567890.123456"
}

Example Usage:

corsair.ts
slack({
    webhookHooks: {
        reactions: {
            added: {
                after: async (ctx, result) => {
                    const { user, reaction, item } = result.data;
                    
                    console.log(`${user} reacted with :${reaction}:`);
                    
                    // Track reactions for analytics
                    await analytics.track("reaction_added", {
                        reaction,
                        channel: item.channel,
                        user,
                    });
                },
            },
        },
    },
})

Key Fields:

FieldTypeDescription
typestringAlways "reaction_added"
userstringUser who added the reaction
reactionstringEmoji name (without colons)
item_userstringUser who created the item
item.typestringItem type (message, file, file_comment)
item.channelstringChannel ID
item.tsstringMessage timestamp
event_tsstringEvent timestamp

teamJoin

users.teamJoin

Event Type: Team Join

Fires when a new user joins the workspace.

When it fires: A new user is added to the workspace

Payload Structure:

{
    type: "team_join",
    user: {
        id: "U1234567890",
        name: "john.doe",
        real_name: "John Doe",
        email: "john@example.com", 
        profile: {
            display_name: "John",
            real_name: "John Doe",
            email: "john@example.com",
            image_72: "https://...",
            // ... more profile fields
        },
        is_bot: false,
        is_admin: false
    },
    cache_ts: 1234567890,
    event_ts: "1234567890.123456"
}

Example Usage:

corsair.ts
slack({
    webhookHooks: {
        users: {
            teamJoin: {
                after: async (ctx, result) => {
                    const user = result.data.user;
                    
                    // Send welcome DM
                    const dm = await ctx.api.channels.open({
                        users: user.id,
                    });
                    
                    await ctx.api.messages.post({
                        channel: dm.channel.id,
                        text: `Welcome to the team, ${user.real_name}! 👋`,
                    });
                },
            },
        },
    },
})

Key Fields:

FieldTypeDescription
typestringAlways "team_join"
user.idstringUser ID
user.namestringUsername
user.real_namestringFull name
user.emailstring?Email address
user.profileobjectUser profile information
user.is_botbooleanWhether user is a bot
event_tsstringEvent timestamp

userChange

users.userChange

Event Type: User Change

Fires when a user's profile information changes.

When it fires:

  • User updates their profile
  • User changes their display name
  • User updates their status
  • Admin updates user information

Payload Structure:

{
    type: "user_change",
    user: {
        id: "U1234567890",
        name: "john.doe",
        real_name: "John Doe",
        profile: {
            status_text: "In a meeting", 
            status_emoji: ":calendar:", 
            display_name: "John",
            email: "john@example.com"
        }
    },
    cache_ts: 1234567890,
    event_ts: "1234567890.123456"
}

Example Usage:

corsair.ts
slack({
    webhookHooks: {
        users: {
            userChange: {
                after: async (ctx, result) => {
                    const user = result.data.user;
                    
                    // Sync user data to your database
                    await syncUserProfile(user);
                    
                    console.log(`User ${user.name} updated their profile`);
                },
            },
        },
    },
})

Key Fields:

FieldTypeDescription
typestringAlways "user_change"
userobjectUpdated user object
user.profileobjectUser profile with changes
event_tsstringEvent timestamp

created

files.created

Event Type: File Created

Fires when a file is uploaded to Slack.

When it fires: A user uploads a new file

Payload Structure:

{
    type: "file_created",
    file_id: "F1234567890",
    user_id: "U1234567890",
    file: {
        id: "F1234567890"
    },
    event_ts: "1234567890.123456"
}

Example Usage:

corsair.ts
slack({
    webhookHooks: {
        files: {
            created: {
                after: async (ctx, result) => {
                    const fileId = result.data.file_id;
                    
                    // Fetch full file details
                    const file = await ctx.api.files.get({
                        file: fileId,
                    });
                    
                    console.log(`New file uploaded: ${file.name}`);
                },
            },
        },
    },
})

Key Fields:

FieldTypeDescription
typestringAlways "file_created"
file_idstringFile ID
user_idstringUser who uploaded the file
event_tsstringEvent timestamp

public

files.public

Event Type: File Made Public

Fires when a file is made publicly accessible.

When it fires: A file's visibility changes to public

Payload Structure:

{
    type: "file_public",
    file_id: "F1234567890",
    user_id: "U1234567890",
    file: {
        id: "F1234567890"
    },
    event_ts: "1234567890.123456"
}

Key Fields:

FieldTypeDescription
typestringAlways "file_public"
file_idstringFile ID
user_idstringUser who made the file public
event_tsstringEvent timestamp

shared

files.shared

Event Type: File Shared

Fires when a file is shared to a channel.

When it fires: A file is posted to a channel

Payload Structure:

{
    type: "file_shared",
    file_id: "F1234567890",
    user_id: "U1234567890",
    file: {
        id: "F1234567890"
    },
    channel_id: "C1234567890", 
    event_ts: "1234567890.123456"
}

Example Usage:

corsair.ts
slack({
    webhookHooks: {
        files: {
            shared: {
                after: async (ctx, result) => {
                    const { file_id, channel_id } = result.data;
                    
                    // Fetch file details
                    const file = await ctx.api.files.get({
                        file: file_id,
                    });
                    
                    console.log(`File shared in channel: ${file.name}`);
                    
                    // Process or scan the file
                    await processSharedFile(file);
                },
            },
        },
    },
})

Key Fields:

FieldTypeDescription
typestringAlways "file_shared"
file_idstringFile ID
user_idstringUser who shared the file
channel_idstringChannel where file was shared
event_tsstringEvent timestamp

Webhook Hooks

Webhook hooks let you add custom logic that runs when Corsair processes a webhook event.

Before Hooks

Run before the webhook is processed. Use them to validate, filter, or modify incoming webhooks.

corsair.ts
slack({
    webhookHooks: {
        messages: {
            message: {
                before: async (ctx, payload) => {
                    // Skip bot messages
                    if (payload.bot_id) {
                        throw new Error("Skipping bot message");
                    }
                    
                    // Modify payload before processing
                    return { ctx, payload };
                },
            },
        },
    },
})

After Hooks

Run after the webhook is processed and the database is updated. Use them for side effects, notifications, or triggering workflows.

corsair.ts
slack({
    webhookHooks: {
        messages: {
            message: {
                after: async (ctx, result) => {
                    // This always runs after the message is saved
                    await notifyTeam(result.data);
                    await updateAnalytics(result.data);
                },
            },
        },
    },
})

Multiple Webhook Hooks

You can add hooks for multiple webhook types:

corsair.ts
slack({
    webhookHooks: {
        messages: {
            message: {
                after: async (ctx, result) => {
                    await processMessage(result.data);
                },
            },
        },
        channels: {
            created: {
                after: async (ctx, result) => {
                    await notifyChannelCreated(result.data);
                },
            },
        },
        reactions: {
            added: {
                after: async (ctx, result) => {
                    await trackReaction(result.data);
                },
            },
        },
    },
})

See Webhooks for more details on webhook concepts and Hooks for the complete hooks documentation.

Configuring Events in Slack

To receive webhook events, you need to configure your Slack app:

  1. Go to your app's settings at api.slack.com/apps

  2. Navigate to Event Subscriptions

  3. Enable events and set your Request URL

  4. Under Subscribe to bot events, add the events you want to receive:

    • message.channels - Messages in public channels
    • message.groups - Messages in private channels
    • message.im - Direct messages
    • message.mpim - Group direct messages
    • channel_created - New channels
    • reaction_added - Reactions added
    • team_join - New team members
    • user_change - User profile changes
    • file_created - Files uploaded
    • file_public - Files made public
    • file_shared - Files shared
  5. Save your changes

Required OAuth Scopes:

Make sure your app has the necessary OAuth scopes:

  • channels:history - Read messages from public channels
  • channels:read - View basic channel information
  • users:read - View users
  • reactions:read - View emoji reactions
  • files:read - View files

See the Slack API documentation for a complete list of events and required scopes.