← Blog

How to Connect Claude API to a Slack Bot

2026-09-23 · 5 min read · SubToAPI Team

Connecting Claude to a Slack bot means building a small server that listens for Slack events (like a mention or a DM), forwards the message text to Claude's messages endpoint, and posts the reply back into the channel or thread. There's no native Slack-to-Claude integration — you're wiring two APIs together yourself, and the whole thing can be running in under an hour once you understand the pieces.

This guide walks through the full path: creating a Slack app, subscribing to the right events, verifying requests, calling Claude, and posting responses back — including how to handle Slack's 3-second acknowledgement window, which is the part most people get stuck on.

The architecture in one sentence

Slack sends an HTTP POST to your server whenever your bot is mentioned or messaged → your server extracts the text → your server calls the Claude API → your server posts the response back to Slack using the chat.postMessage API (not the original webhook response, because Claude takes longer than 3 seconds to reply).

That last point matters. Slack's Events API requires you to acknowledge the incoming event within 3 seconds, but a Claude completion can take several seconds longer, especially for longer responses. So the pattern is: acknowledge immediately with a 200, then call Claude asynchronously and post the result with a separate API call.

Step 1: Create the Slack app

  1. Go to api.slack.com/apps and create a new app "from scratch."
  2. Under OAuth & Permissions, add these bot token scopes:
  1. Install the app to your workspace and copy the Bot User OAuth Token (starts with xoxb-).
  2. Under Event Subscriptions, enable events and set your Request URL to your server's endpoint (e.g. https://yourapp.com/slack/events). Subscribe to app_mention and message.im.

Slack will send a verification challenge to your endpoint the first time you save this — your server needs to echo back the challenge value to confirm ownership.

Step 2: Set up the server

A minimal Express server handling Slack's URL verification and events:

import express from "express";
import { WebClient } from "@slack/web-api";

const app = express();
app.use(express.json());

const slack = new WebClient(process.env.SLACK_BOT_TOKEN);

app.post("/slack/events", async (req, res) => {
  if (req.body.type === "url_verification") {
    return res.send(req.body.challenge);
  }

  res.sendStatus(200); // acknowledge immediately

  const event = req.body.event;
  if (event && event.type === "app_mention") {
    handleMention(event);
  }
});

async function handleMention(event) {
  const reply = await askClaude(event.text);
  await slack.chat.postMessage({
    channel: event.channel,
    thread_ts: event.ts,
    text: reply,
  });
}

app.listen(3000);

The important detail here is res.sendStatus(200) happening before askClaude resolves. If you await Claude before responding to Slack, Slack will time out and retry the same event, and you'll end up processing it multiple times.

Step 3: Call Claude

The askClaude function is just a call to the Messages API. If you're calling Anthropic directly, that means managing an API key, request signing, and your own retry/error handling. If you're using SubToAPI, the same call works over HTTPS with a sub_live_ key and standard Bearer auth:

async function askClaude(text) {
  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-5",
      max_tokens: 1024,
      messages: [{ role: "user", content: text.replace(/<@\w+>/, "").trim() }],
    }),
  });

  const data = await res.json();
  return data.content[0].text;
}

Note the .replace(/<@\w+>/, "") — Slack includes the bot's user ID mention (<@U12345>) at the start of the message text, so you need to strip it before sending the text to Claude.

This setup is useful if you're building an internal Slack bot on a team plan and don't want every developer holding a raw Anthropic key. With SubToAPI's application keys you can issue one key per bot or per environment, see usage per key in the dashboard, and rotate a compromised key without breaking your whole Slack integration. Check /docs/quickstart for the auth flow and /docs/messages for the full request schema.

Step 4: Handle threads and context

Slack conversations are threaded, and Claude has no memory between requests — each call is stateless. If you want the bot to remember earlier messages in a thread, you need to fetch the thread history from Slack (conversations.replies) and pass it into the messages array yourself:

const history = await slack.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,
}));

This is the same pattern as any chat app — you're reconstructing conversation state on every turn because the API itself doesn't store it.

Step 5: Streaming (optional)

Slack messages can be edited after posting, which lets you fake streaming: post an initial "thinking…" message, then use chat.update to rewrite it as tokens arrive. This is a nice UX touch for longer responses but adds complexity — most teams skip it for internal bots and only bother once response length starts feeling slow. If you do want it, /docs/streaming covers how streamed responses are structured on the SubToAPI side.

Common mistakes to avoid

Wrapping up

The core integration is small: an events endpoint, a call to Claude, and a chat.postMessage call back. Most of the real work is in message formatting, thread context, and making sure you acknowledge Slack fast enough. If you'd rather not manage Anthropic credentials directly across your bot and other internal tools, /signup gets you a SubToAPI key in a couple of minutes, and /pricing has the plan breakdown if you're running this for a team.

questions

Do I need an Anthropic account to build a Slack bot with Claude? Yes, you need access to Claude's API somehow — either directly through Anthropic or through a proxy service like SubToAPI that issues its own API keys against your existing access.

Can the Slack bot remember previous messages in a thread? Not automatically. Claude is stateless per request, so you need to fetch the thread history from Slack's conversations.replies API and include it in the messages array on each call.

Why does my bot reply late or not at all sometimes? Usually because your server is awaiting the Claude response before acknowledging Slack's event, which causes a timeout and retry. Always send a 200 response first, then process the Claude call asynchronously.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →