Claude API Slack Bot Integration Tutorial
Claude API Slack Bot Integration Tutorial
If you're searching for this, you probably want one of two things: a bot that answers questions in a Slack channel using Claude, or a way to pipe Slack messages into Claude and post the response back as a thread reply. Both use the same core pattern — a Slack app with event subscriptions, a small server that receives those events, a call to the Claude API, and a message posted back through Slack's Web API.
This tutorial walks through that pattern end to end with working code, using Node.js and the Slack Bolt SDK. You can swap Bolt for any Slack client library — the Claude integration part stays the same.
What you'll build
A Slack bot that:
- Listens for
@mentionsin any channel it's invited to - Sends the message text (plus recent thread context) to Claude
- Posts the reply back in the same thread
- Handles Slack's 3-second ack requirement without timing out
Prerequisites
- A Slack workspace where you can install apps
- Node.js 18+
- A Claude API key — either directly from Anthropic or an application key like
sub_live_...from a service like SubToAPI, which wraps your existing Claude access in a standard HTTPS API. For a Slack bot that multiple teammates maintain, having a shared key with usage visibility in one dashboard is genuinely easier than passing around a personal credential.
Step 1: Create the Slack app
Go to api.slack.com/apps → Create New App → From scratch. Give it a name and pick your workspace.
Under OAuth & Permissions, add these bot token scopes:
app_mentions:readchat:writechannels:history(if you want thread context)
Install the app to your workspace and copy the Bot User OAuth Token (xoxb-...).
Under Event Subscriptions, enable events and subscribe to app_mention. If you don't have a public HTTPS endpoint yet, use Socket Mode instead — it avoids exposing a public URL during development. Enable Socket Mode and generate an App-Level Token (xapp-...).
Step 2: Scaffold the bot server
mkdir claude-slack-bot && cd claude-slack-bot
npm init -y
npm install @slack/bolt node-fetch
// index.js
const { App } = require("@slack/bolt");
const fetch = require("node-fetch");
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
appToken: process.env.SLACK_APP_TOKEN,
socketMode: true,
});
app.event("app_mention", async ({ event, client }) => {
const userText = event.text.replace(/<@[^>]+>/, "").trim();
const claudeReply = await askClaude(userText);
await client.chat.postMessage({
channel: event.channel,
thread_ts: event.thread_ts || event.ts,
text: claudeReply,
});
});
async function askClaude(prompt) {
const res = 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",
max_tokens: 500,
messages: [{ role: "user", content: prompt }],
}),
});
const data = await res.json();
return data.content?.[0]?.text ?? "Sorry, I couldn't generate a reply.";
}
(async () => {
await app.start();
console.log("Slack bot running");
})();
This uses SubToAPI's /v1/messages endpoint as a drop-in Claude backend — if you'd rather call Anthropic's API directly, the request shape is nearly identical. The full request and response schema is in the messages docs; the quickstart covers authentication setup if you're starting from zero.
Run it:
SLACK_BOT_TOKEN=xoxb-... \
SLACK_APP_TOKEN=xapp-... \
SUBTOAPI_KEY=sub_live_... \
node index.js
Invite the bot to a channel, mention it, and you should get a threaded reply within a couple of seconds.
Step 3: Handle Slack's ack timeout
Slack expects an acknowledgment within 3 seconds, but Claude generations — especially longer ones — can take longer than that. If you're on HTTP Events (not Socket Mode), ack immediately and process asynchronously:
app.event("app_mention", async ({ event, client, ack }) => {
if (ack) await ack();
// ... rest of the handler
});
With Socket Mode this is less of an issue since Bolt handles acking for you, but it's still good practice to post a quick "Thinking…" placeholder message and edit it once Claude responds, so users aren't staring at silence.
Step 4: Stream the response for long replies
For longer answers, streaming tokens into an editable Slack message feels much more responsive than waiting for the full completion. Slack doesn't support true token-by-token streaming in a message, but you can batch updates every few hundred milliseconds:
let buffer = "";
let lastUpdate = Date.now();
// inside your streaming handler, on each chunk:
buffer += chunk;
if (Date.now() - lastUpdate > 800) {
await client.chat.update({ channel, ts: messageTs, text: buffer });
lastUpdate = Date.now();
}
SubToAPI's /v1/messages endpoint supports stream: true with server-sent events — see the streaming docs for the event format if you want to implement this properly instead of polling.
Step 5: Add thread context (optional but recommended)
A bot that only sees the single mentioned message feels dumb in ongoing conversations. Fetch the thread history and include it as prior messages:
const history = await client.conversations.replies({
channel: event.channel,
ts: event.thread_ts || event.ts,
});
const messages = history.messages.map((m) => ({
role: m.bot_id ? "assistant" : "user",
content: m.text,
}));
Pass messages as the array in your Claude request instead of a single-item array.
Step 6: Add tool use for real actions
If your bot should do things — look up a ticket, query a database, post to a calendar — use Claude's tool use feature rather than trying to parse intent yourself. Define tool schemas, let Claude decide when to call them, and execute the actual Slack or API action in your handler. The tool use docs cover the request/response format for defining and handling tool calls.
Step 7: Deploy
For production, swap Socket Mode for an HTTP endpoint (needed if you want multiple instances or want to avoid the Socket Mode connection limits), deploy on any Node host, and set your Slack app's Event Subscriptions URL to that endpoint. Store your Slack tokens and Claude API key as environment secrets, not in code.
If you're managing this bot across a team, a service with per-key usage metadata and seat-based access — like SubToAPI's Team plan — makes it easier to see which bot or environment is consuming tokens without digging through raw API logs.
Questions
Does this work with Slack's Socket Mode or only HTTP events? Both. Socket Mode is simpler for local development since it doesn't need a public URL; HTTP events are better for production deployments with multiple instances.
Can the bot remember conversation history across messages? Yes — fetch the thread's prior messages with conversations.replies and pass them as the messages array in your Claude request, mapping bot messages to the assistant role.
Do I need an Anthropic API key directly, or can I use a proxy? Either works. A proxy like SubToAPI gives you a standard HTTPS API key (sub_live_...), streaming, and usage tracking without changing how your bot code is structured — see the quickstart to get a key via signup.