Connecting the Claude Agent SDK to Slack
Create a Slack app, configure bot scopes and socket mode, install Slack Bolt, and wire up channel mentions and DMs. The full integration from app creation to first response.
Two requirements before you write code
An Anthropic API key. Your Claude.ai subscription works for local development. A deployed Slack bot runs in the cloud without a subscription session — it needs an API key. Get one from console.anthropic.com. Treat it like a password.
A Slack workspace. Everything in this guide works on Slack's free plan.
Creating your Slack app
Go to api.slack.com/apps and click Create New App → From Scratch. Name it anything. Select your workspace.
Enable socket mode
Under Socket Mode, turn it on and generate a token. This is your SLACKAPPTOKEN. Copy it immediately.
Add bot token scopes
Under OAuth & Permissions → Bot Token Scopes, add these five scopes:
app_mentions:readchat:writeim:historyim:readim:write
Save changes, then click Install to Workspace. After installing, copy the Bot User OAuth Token — this is your SLACKBOTTOKEN.
Subscribe to events
Under Event Subscriptions, enable events and subscribe to:
app_mentionmessage.im
Enable DMs
Under App Home → Messages Tab, enable Allow users to send Slash commands and messages from the messages tab.
Setting up your environment
Create a .env file in your project root:
ANTHROPIC_API_KEY=sk-ant-...
SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-...
Add this to your .gitignore:
.env
Never commit these values. If one ends up in a public repository, rotate it immediately.
Installing Slack Bolt
bun add @slack/bolt
Writing the bot server
Create src/bot.ts:
import { App } from "@slack/bolt";
import { agentChat } from "./agent";
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
appToken: process.env.SLACK_APP_TOKEN,
socketMode: true,
});
// Handle @mentions in channels
app.event("app_mention", async ({ event, say }) => {
const threadTs = event.thread_ts ?? event.ts;
try {
const { response } = await agentChat(event.text);
await say({ text: response, thread_ts: threadTs });
} catch (error) {
await say({ text: "Something went wrong.", thread_ts: threadTs });
}
});
// Handle DMs
app.event("message", async ({ event, say }) => {
// Prevent infinite loops — skip bot messages
if ("bot_id" in event) return;
const threadTs = "thread_ts" in event ? event.thread_ts ?? event.ts : undefined;
try {
const { response } = await agentChat(
"text" in event ? event.text ?? "" : ""
);
await say({ text: response, thread_ts: threadTs });
} catch (error) {
await say({ text: "Something went wrong.", thread_ts: threadTs });
}
});
async function startBot() {
await app.start();
console.log("Bot is running.");
}
startBot();
The bot message guard is essential. Without the if ("bot_id" in event) return check, every response your bot posts triggers the message event again. You get an infinite loop that burns through API credits in seconds.
Running the bot
bun src/bot.ts
Go to your Slack workspace and @mention your app in a channel. Add the bot to the channel when prompted. You should see a response within a few seconds.
For DMs: find your app in the Slack sidebar under Apps, send a direct message.
What you have at this point
Channel mentions and DMs both work. The agent responds to every message. Each response is threaded under the original message.
What is not working yet:
- Session persistence (each message starts a fresh conversation)
- A thinking indicator
- Slack-formatted markdown in responses
Those are covered in the Polishing the Slack Experience and Session Persistence guides.
> Real-world note: This same Bolt setup works for other messaging services. Discord, Telegram, and Microsoft Teams all have similar event-driven APIs. Once you understand the Slack pattern — receive event, call agentChat, post response — adapting it to another service is mostly swapping out the event handlers and posting functions.
---
Author: FractionalSkill