Connect Slack to your agent or app. Corsair handles auth, data syncing, and webhook verification — you write the logic.
What you get out of the box:
- Send and receive messages
- Query channels, users, files, and reactions — live or from local cache
- Real-time webhook events (messages, joins, reactions, and more)
- Automatic signature verification for every incoming event
Setup
Install
pnpm install @corsair-dev/slack
Add the plugin
import { createCorsair } from 'corsair';
import { slack } from '@corsair-dev/slack';
import Database from 'better-sqlite3';
export const corsair = createCorsair({
plugins: [slack()],
database: new Database('corsair.db'),
kek: process.env.CORSAIR_KEK!,
});
Get your credentials
You need a Slack bot token (xoxb-...) to make API calls, and a signing secret to verify incoming webhooks.See Get Credentials for step-by-step instructions on creating a Slack app and copying both values. Store credentials
pnpm corsair setup --slack bot_token=xoxb-your-bot-token webhook_signature=your-signing-secret
Run this once. Credentials are encrypted with your KEK and stored in your database.
Authentication
The Slack plugin supports two auth methods:
API Key (Bot Token)
OAuth 2.0
The default. Create a Slack app, install it to your workspace, and copy the bot token.slack({
authType: 'api_key', // default, can be omitted
})
Store the token:pnpm corsair setup --slack bot_token=xoxb-your-bot-token
See Get Credentials for where to find this in the Slack dashboard. Use OAuth when users connect their own Slack workspaces (multi-tenant apps).slack({
authType: 'oauth_2',
})
Store your OAuth app credentials, then start the flow:pnpm corsair setup --slack client_id=your-client-id client_secret=your-client-secret
pnpm corsair auth --plugin=slack
The CLI will print an authorization URL. Once the user approves, tokens are stored automatically per tenant. See Multi-Tenancy for the full setup.
Send a message
await corsair.slack.api.messages.post({
channel: 'C1234567890', // channel ID
text: 'Hello from Corsair!',
});
Use channel IDs, not names
Slack’s API requires channel IDs (e.g. C1234567890), not names like #general. Query corsair.slack.api.channels.list() once to get the IDs, then cache them locally.
Query data
Corsair syncs Slack data to your local database. Query it instantly — no API call needed every time.
Direct Slack API calls — always fresh, always counts against rate limits:// List channels
const channels = await corsair.slack.api.channels.list({
exclude_archived: true,
});
// List users
const users = await corsair.slack.api.users.list();
// Post a message
await corsair.slack.api.messages.post({
channel: 'C1234567890',
text: 'Hello!',
});
Every API call automatically saves the response to your local database. Query your local database — instant, no rate limit, works offline:// All channels you've fetched
const channels = await corsair.slack.db.channels.findAll();
// Find a specific channel by name
const results = await corsair.slack.db.channels.findMany({
where: { data: { name: { contains: 'general' } } },
});
// Find messages in a channel
const messages = await corsair.slack.db.messages.findMany({
where: { data: { channel: 'C1234567890' } },
});
// Find a user by name
const users = await corsair.slack.db.users.findMany({
where: { data: { name: { contains: 'john' } } },
});
See Database Reference for full query options.
Receive webhooks
Set up a single webhook endpoint — Corsair routes and verifies everything:
import { processWebhook } from 'corsair';
import { corsair } from '@/server/corsair';
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const headers: Record<string, string> = {};
request.headers.forEach((value, key) => { headers[key] = value; });
const body = request.headers.get('content-type')?.includes('application/json')
? await request.json()
: await request.text();
const tenantId = new URL(request.url).searchParams.get('tenantId') ?? undefined;
const result = await processWebhook(corsair, headers, body, { tenantId });
if (!result.response) {
return NextResponse.json({ success: false }, { status: 404 });
}
return NextResponse.json(result.response);
}
React to events with webhook hooks:
slack({
webhookHooks: {
messages: {
message: {
after: async (ctx, result) => {
// Skip bot messages
if (result.data.bot_id) return;
console.log(`Message from ${result.data.user}: ${result.data.text}`);
},
},
},
users: {
teamJoin: {
after: async (ctx, result) => {
// Welcome new team members
await corsair.slack.api.messages.post({
channel: 'C_GENERAL_CHANNEL_ID',
text: `Welcome <@${result.data.user.id}>!`,
});
},
},
},
},
})
Available webhook events:
| Event | Trigger |
|---|
messages.message | New or updated message |
channels.created | New channel created |
reactions.added | Reaction added to message |
users.teamJoin | New user joined team |
users.userChange | User profile updated |
files.created | File uploaded |
challenge.challenge | URL verification (handled automatically) |
See Slack Webhooks for full payload shapes.
Plugin options
slack({
authType: 'api_key', // 'api_key' | 'oauth_2'
key: process.env.SLACK_BOT_TOKEN, // optional, uses DB otherwise
signingSecret: process.env.SLACK_SIGNING_SECRET, // optional, uses DB otherwise
hooks: { /* endpoint hooks */ },
webhookHooks: { /* webhook hooks */ },
errorHandlers: { /* custom error handlers */ },
})
| Option | Type | Description |
|---|
authType | 'api_key' | 'oauth_2' | Authentication method (default: 'api_key') |
key | string | Bot token — use DB storage instead for production |
signingSecret | string | Webhook signing secret — use DB storage instead for production |
hooks | object | Logic that runs before/after API calls |
webhookHooks | object | Logic that runs before/after webhook events |
errorHandlers | object | Custom error handling per error type |
Multi-tenancy
If you’re building a product where users connect their own Slack workspaces:
export const corsair = createCorsair({
multiTenancy: true,
plugins: [slack()],
// ...
});
const tenant = corsair.withTenant('user_abc123');
// All calls scoped to this user
await tenant.slack.api.messages.post({
channel: 'C1234567890',
text: 'Hello!',
});
const channels = await tenant.slack.db.channels.findAll();
Point each user’s Slack webhook to your endpoint with their ID:
https://your-app.com/api/webhook?tenantId=user_abc123
See Multi-Tenancy for the full guide.
What’s next
Get Credentials
Create a Slack app and copy your bot token and signing secret.
API Endpoints
All available Slack API operations and their parameters.
Webhooks
All Slack events, payload shapes, and verification details.
Database
Query synced Slack data from your local database.