Claude API Slack Bot: Step by Step Setup Guide
Building a Slack bot with the Claude API means connecting Slack's Events API to a backend that calls Claude and posts the response back into a channel or thread. This guide walks through every step: creating the Slack app, setting up event subscriptions, writing the handler code, and calling Claude to generate replies.
The whole thing is roughly 150 lines of code and runs on any server or serverless function that can receive HTTPS webhooks. You don't need a framework — just a web server, a Slack bot token, and a way to call Claude.
What you need before starting
- A Slack workspace where you can install apps (or create a free dev workspace)
- A Claude API key — either directly from Anthropic or through a proxy like SubToAPI if you want one key, usage dashboards, and team seats across projects
- A server reachable over HTTPS (ngrok works fine for local testing)
- Node.js or any language with an HTTP client
Step 1: Create the Slack app
- Go to api.slack.com/apps and click Create New App → From scratch.
- Name it (e.g. "Claude Assistant") and pick your workspace.
- Under OAuth & Permissions, add these bot token scopes:
chat:writeapp_mentions:readchannels:history(if you want it to read channel context)
- Install the app to your workspace. Copy the Bot User OAuth Token (
xoxb-...).
Step 2: Enable event subscriptions
- Under Event Subscriptions, toggle it on.
- Set the Request URL to your server endpoint, e.g.
https://yourserver.com/slack/events. Slack will send a verification challenge here first — your server needs to echo it back. - Subscribe to the bot event
app_mention(fires when someone @mentions your bot) and optionallymessage.channelsif you want it to respond to all messages in channels it's invited to. - Save changes and reinstall the app if prompted.
Step 3: Set up the server to verify Slack requests
Slack signs every request with a signing secret. Verify it before processing anything — this prevents spoofed requests.
import crypto from "crypto";
function verifySlackRequest(req, signingSecret) {
const timestamp = req.headers["x-slack-request-timestamp"];
const sig = req.headers["x-slack-signature"];
const baseString = `v0:${timestamp}:${req.rawBody}`;
const hmac = crypto
.createHmac("sha256", signingSecret)
.update(baseString)
.digest("hex");
const expected = `v0=${hmac}`;
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}
Handle the URL verification challenge first:
app.post("/slack/events", (req, res) => {
if (req.body.type === "url_verification") {
return res.send(req.body.challenge);
}
// ... rest of handling below
});
Step 4: Handle the mention event and call Claude
When app_mention fires, Slack sends the message text (including the mention), the channel, and a thread timestamp. Strip the mention, send the text to Claude, and post the reply back.
app.post("/slack/events", async (req, res) => {
res.sendStatus(200); // ack immediately, Slack requires a fast response
const event = req.body.event;
if (event?.type !== "app_mention") return;
const text = event.text.replace(/<@[^>]+>/, "").trim();
const claudeRes = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: text }],
}),
});
const data = await claudeRes.json();
const reply = data.content[0].text;
await fetch("https://slack.com/api/chat.postMessage", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SLACK_BOT_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
channel: event.channel,
thread_ts: event.thread_ts || event.ts,
text: reply,
}),
});
});
Note the pattern: ack Slack's request immediately (within 3 seconds, or Slack retries and you get duplicate replies), then do the Claude call and post the result asynchronously.
If you're using SubToAPI instead of calling Anthropic directly, you get a standard HTTPS endpoint at /docs/messages with the same request shape, plus usage metadata per request so you can track exactly how much each Slack bot call costs without digging through logs.
Step 5: Keep conversation context in threads
Slack threads map naturally to Claude conversations. Store the message history per thread_ts (in memory for testing, Redis or a database for production) and pass the full history as the messages array on each call instead of just the latest message. This lets the bot remember earlier turns in the same thread.
const threadHistory = threadStore.get(event.thread_ts) || [];
threadHistory.push({ role: "user", content: text });
const data = await callClaude(threadHistory);
threadHistory.push({ role: "assistant", content: data.content[0].text });
threadStore.set(event.thread_ts, threadHistory);
Step 6: Deploy and test
Deploy your handler (Vercel, Railway, a small VPS, whatever you already use), point the Slack Request URL at the live endpoint, and invite the bot to a channel with /invite @YourBotName. Mention it and confirm you get a reply within a few seconds.
For production, consider:
- Streaming: if replies run long, post an initial "thinking..." message and edit it with
chat.updateas tokens arrive — see /docs/streaming for the SSE format. - Tool use: if the bot needs to look things up (ticket status, docs search, etc.), wire in Claude's tool calling — covered in /docs/tools.
- Key management: if multiple teammates are building bots on the same Claude access, a shared gateway with per-project API keys (
sub_live_...) avoids everyone sharing one raw Anthropic key. Set this up in minutes at /signup.
questions
Do I need a paid Slack plan to run a Claude bot? No. The Events API, bot tokens, and chat.postMessage all work on Slack's free tier. You only need a paid plan for features unrelated to bots, like extended message history for human users.
Why does my bot reply twice to the same mention? Slack retries the event if your server doesn't return a 200 response within 3 seconds. Always acknowledge the request immediately and do the Claude call afterward, as shown in Step 4.
Can I use this same setup for Microsoft Teams or Discord? The Claude-calling logic is identical — only the webhook format and message-posting API differ. Swap Slack's Events API and chat.postMessage for the equivalent endpoints in Teams or Discord's bot framework.