Claude API Slack Bot Setup Guide
Building a Slack bot that talks to Claude means connecting three pieces: a Slack app that listens for messages, a small server that receives those events, and an API call to Claude that generates a reply. This guide walks through each piece in order, from creating the Slack app to deploying a working bot that responds in channels and DMs.
If you just want the fastest path, skip to the "API key" step — you can use either a direct Anthropic API key or an application key from a gateway like SubToAPI, which adds HTTPS streaming, usage metadata per request, and team seats if more than one person needs to manage the bot.
What you need before starting
- A Slack workspace where you have permission to install apps
- A place to run a small HTTP server (a serverless function, a VPS, or a free-tier host — anything reachable over HTTPS)
- Node.js 18+ (the examples below use it, but any language works the same way)
- A Claude API key
Step 1: Create the Slack app
- Go to api.slack.com/apps and click Create New App, then From scratch.
- Name it and pick your workspace.
- Under OAuth & Permissions, add these bot token scopes:
chat:writeapp_mentions:readchannels:history(if you want it to read channel messages, not just mentions)
- Install the app to your workspace. Copy the Bot User OAuth Token (
xoxb-...) — your server will use this to post replies.
Step 2: Turn on Event Subscriptions
In Event Subscriptions, enable events and set the Request URL to your server's endpoint (e.g. https://yourserver.com/slack/events). Slack will send a verification challenge first — your server needs to echo it back before the URL is accepted.
Subscribe to:
app_mention— fires when someone @-mentions your botmessage.im— fires for direct messages to the bot
Step 3: Get a Claude API key
You have two options here:
Direct Anthropic API key — works fine for a single bot with light usage, but you're on your own for rate limit handling, usage tracking per channel/team, and key rotation.
SubToAPI application key — a sub_live_... key that proxies to Claude with the same request/response shape, but adds streaming support, per-request usage metadata (so you can log token counts per Slack workspace or channel), and team seats if more than one developer maintains the bot. Get a key from /signup and check /pricing for plans (Solo €9, Team €19/seat, Scale €49/seat).
Either way, store the key as an environment variable — never hardcode it.
Step 4: Write the bot server
Here's a minimal Express server using Slack's Bolt framework and the Claude API:
const { App } = require("@slack/bolt");
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET,
socketMode: false,
});
app.event("app_mention", async ({ event, say }) => {
const userText = event.text.replace(/<@[^>]+>/, "").trim();
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: userText }],
}),
});
const data = await response.json();
const reply = data.content[0].text;
await say({ text: reply, thread_ts: event.thread_ts || event.ts });
});
(async () => {
await app.start(process.env.PORT || 3000);
console.log("Slack bot running");
})();
A few things to note:
- Replying in a thread (
thread_ts) keeps channels readable, especially if multiple people are pinging the bot. - Slack expects a response to its event webhook within 3 seconds, but Claude responses can take longer. Bolt's event handler already acknowledges the request separately from your async work, so this is usually fine — just don't block on anything else before calling
say. - For DMs, add a
message.imlistener the same way, checkingevent.channel_type === "im".
Step 5: Add streaming for longer responses
If your bot often generates long answers, streaming avoids the Slack message appearing all at once after a long pause. SubToAPI supports streaming responses — see /docs/streaming for the event format. You can post an initial placeholder message, then edit it with chat.update as chunks arrive, giving users a visible "typing" effect instead of silence.
Step 6: Deploy and verify
- Deploy your server somewhere with a stable HTTPS URL (a serverless function works, but watch for cold starts delaying the first response).
- Set the Event Subscriptions URL in the Slack app config to your live endpoint.
- Mention the bot in a test channel and confirm it replies in-thread.
- Check your API provider's dashboard to confirm requests are landing — if you're using SubToAPI, usage and token counts per request show up immediately, which is useful for figuring out which channels are driving cost.
Tool use and context
If you want the bot to do more than chat — look up a ticket, query a database, post a formatted card — wire up Claude's tool use feature instead of parsing free text yourself. See /docs/tools for the request format, and /docs/messages for the full message schema including system prompts, which are useful for giving the bot a consistent persona across every channel it's in.
Questions
Do I need a paid Slack plan to run a Claude bot? No. Event Subscriptions, bot tokens, and the scopes used here all work on Slack's free tier. You only need a paid Slack plan for other unrelated features.
Can one bot serve multiple Slack workspaces? Yes, but you need to implement OAuth installation flow (Slack's "distribute app" setup) and store a separate bot token per workspace. For a single internal team, the single-workspace setup above is enough.
How do I keep conversation context across messages in a thread? Store recent messages keyed by thread_ts in a lightweight store (Redis, a database table, or even in-memory for low traffic) and include them in the messages array on each Claude API call — the API itself is stateless between requests.