Skip to main content

Installation

npm install @chat-adapter/slack

Environment Variables

Get these credentials from your Slack app dashboard.
VariableRequiredDescription
SLACK_BOT_TOKENSingle-workspace onlyBot User OAuth Token (xoxb-...)
SLACK_SIGNING_SECRETYesWebhook verification secret
SLACK_CLIENT_IDMulti-workspace onlyOAuth Client ID for app installation
SLACK_CLIENT_SECRETMulti-workspace onlyOAuth Client Secret

Configuration Options

interface SlackAdapterConfig {
  /** Bot token (xoxb-...). Required for single-workspace mode. Omit for multi-workspace. */
  botToken?: string;
  /** Bot user ID (will be fetched if not provided) */
  botUserId?: string;
  /** Slack app client ID (required for OAuth / multi-workspace) */
  clientId?: string;
  /** Slack app client secret (required for OAuth / multi-workspace) */
  clientSecret?: string;
  /** Base64-encoded 32-byte AES-256-GCM encryption key for encrypting stored tokens */
  encryptionKey?: string;
  /** Prefix for state keys storing installations (default: 'slack:installation') */
  installationKeyPrefix?: string;
  /** Logger instance */
  logger: Logger;
  /** Signing secret for webhook verification */
  signingSecret: string;
  /** Override bot username (optional) */
  userName?: string;
}

Setup

For internal bots serving one workspace:
import { Chat } from 'chat';
import { createSlackAdapter } from '@chat-adapter/slack';
import { MemoryState } from '@chat-adapter/state-memory';

const chat = new Chat({
  userName: 'my-bot',
  adapters: {
    slack: createSlackAdapter({
      botToken: process.env.SLACK_BOT_TOKEN!,
      signingSecret: process.env.SLACK_SIGNING_SECRET!,
    }),
  },
  state: new MemoryState(),
});

await chat.initialize();

OAuth Flow (Multi-Workspace)

For multi-workspace apps, handle the OAuth callback:
// Route: /slack/oauth/callback
app.get('/slack/oauth/callback', async (req, res) => {
  const { teamId, installation } = await slack.handleOAuthCallback(req);
  
  res.send(`Installed to workspace ${installation.teamName}!`);
});
The adapter automatically:
  1. Exchanges the authorization code for an access token
  2. Stores the installation via StateAdapter.set()
  3. Encrypts the token if encryptionKey is provided

Webhook Handler

app.post('/webhooks/slack', async (req, res) => {
  const response = await slack.handleWebhook(req, {
    waitUntil: (promise) => {/* handle async work */},
  });
  res.status(response.status).send(await response.text());
});

Features

Supported Events

  • message - Regular messages in channels/DMs
  • app_mention - Bot mentions (@bot-name)
  • reaction_added / reaction_removed - Emoji reactions
  • assistant_thread_started - Slack AI Assistant thread created
  • app_home_opened - User opens bot’s Home tab
  • member_joined_channel - User/bot joins a channel

Slash Commands

chat.onSlashCommand('/deploy', async (event) => {
  await event.channel.post(`Deploying ${event.text}...`);
});

Interactive Components

Button clicks are automatically routed to chat.onAction():
chat.onAction('approve_button', async (event) => {
  await event.thread.post('Approved!');
});

Modals

Modals are not yet fully implemented in the SDK. Use publishHomeView() for Home tab views.

Home Tab

Publish a custom Home tab view:
await slack.publishHomeView(userId, {
  type: 'home',
  blocks: [
    {
      type: 'section',
      text: { type: 'mrkdwn', text: 'Welcome to *My Bot*!' },
    },
  ],
});

Slack Assistant API

For Slack AI features:
// Set suggested prompts
await slack.setSuggestedPrompts(
  channelId,
  threadTs,
  [
    { title: 'Summarize', message: '/summarize this thread' },
    { title: 'Help', message: 'What can you do?' },
  ],
  'Quick actions'
);

// Show thinking indicator
await slack.setAssistantStatus(channelId, threadTs, 'is thinking...');

// Set thread title (appears in History)
await slack.setAssistantTitle(channelId, threadTs, 'Q&A about deployment');

Thread IDs

Slack thread IDs encode channel and message timestamp:
slack:{channelId}:{threadTs}
Examples:
  • Channel message: slack:C1234567890:1234567890.123456
  • DM (top-level): slack:D9876543210: (empty threadTs for DM roots)
  • DM reply: slack:D9876543210:1234567890.123456

Platform Limits

  • Message length: 40,000 characters (Block Kit) or 4,000 characters (plain text)
  • Blocks per message: 50 blocks
  • Rate limits: Tier-based (1+ requests/second for most methods)
  • File size: 1 GB per file
See Slack rate limits for details.

Code Examples

chat.onNewMention(async (event) => {
  await event.thread.post(`Hi ${event.message.author.userName}!`);
});

Troubleshooting

  • Ensure SLACK_SIGNING_SECRET matches your app configuration
  • Check that the request body is raw (not parsed JSON)
  • Verify timestamp is within 5 minutes (check server clock)
  • Add bot to the channel (/invite @bot-name)
  • Verify bot has chat:write scope
  • Check webhook URL is publicly accessible
  • Enable message.channels event subscription
  • Use a persistent StateAdapter (Redis, not Memory)
  • Ensure OAuth redirect URL is whitelisted in app settings
  • Check that installations are being saved (debug logs)

Required Scopes

Add these OAuth scopes to your Slack app: Bot Token Scopes:
  • chat:write - Post messages
  • reactions:write - Add reactions
  • channels:history - Read channel messages
  • groups:history - Read private channel messages
  • im:history - Read DM messages
  • mpim:history - Read group DM messages
  • users:read - Look up user info
Event Subscriptions:
  • message.channels
  • message.groups
  • message.im
  • message.mpim
  • app_mention
  • reaction_added
  • reaction_removed
See Slack permissions for full scope list.

Next Steps

Message Handling

Process messages and build conversation flows

Block Kit Cards

Create interactive UIs with buttons and forms

Multi-Workspace Apps

Build apps for public distribution

State Management

Persist data across requests