Slack
Extend Corsair to Slack Integrations
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/slackAdd 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-secretRun this once. Credentials are encrypted with your KEK and stored in your database.
Authentication
The Slack plugin supports two auth methods:
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-tokenSee 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=slackThe 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_abc123See Multi-Tenancy for the full guide.