← Blog

Claude API for Slack Bot Integration: A Setup Guide

2026-10-25 · 5 min read · SubToAPI Team

Building a Slack bot powered by Claude means wiring together three systems: Slack's Events API (to receive messages), your bot server (to process them), and the Claude API (to generate responses). The core integration pattern is the same regardless of language or framework: Slack sends an event webhook, your server extracts the message text, calls Claude, and posts the reply back to the channel via Slack's chat.postMessage endpoint.

The tricky parts aren't the AI calls themselves — they're the operational details: Slack's 3-second webhook timeout, managing conversation context across threads, handling retries when Claude's API throttles you, and keeping API keys secure across multiple environments if you have a team building the bot. This guide covers the full flow and the gotchas that trip people up in production.

Architecture Overview

A typical Claude-powered Slack bot has four pieces:

  1. Slack App — configured with Event Subscriptions (or Socket Mode) to receive message or app_mention events
  2. Bot server — a small HTTP service (Node, Python, etc.) that receives Slack events, calls Claude, and sends a reply
  3. Claude API access — either direct Anthropic API keys or a proxy layer like SubToAPI that gives you sub_live_... keys with usage tracking
  4. Response delivery — Slack's Web API (chat.postMessage) or a response_url for slash commands

The critical constraint is Slack's webhook timeout: if you don't acknowledge the event within 3 seconds, Slack retries the delivery, which can cause duplicate bot replies. The fix is to acknowledge immediately and process asynchronously.

Step 1: Acknowledge Fast, Process Async

app.post('/slack/events', async (req, res) => {
  // Acknowledge Slack immediately
  res.status(200).send();

  const event = req.body.event;
  if (event.type === 'app_mention' || event.type === 'message') {
    // Process in the background — don't block the response
    handleSlackMessage(event).catch(console.error);
  }
});

Never await the Claude call before responding to Slack. Fire the 200 OK first, then run the LLM call and post the reply separately with chat.postMessage.

Step 2: Call Claude and Reply

Once you've acknowledged the event, call the Claude API and post the result back to the same channel/thread:

async function handleSlackMessage(event) {
  const response = 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: event.text }]
    })
  });

  const data = await response.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.ts,
      text: reply
    })
  });
}

Using thread_ts keeps the bot's reply in a thread rather than cluttering the main channel, which matters once your bot gets used frequently.

Step 3: Maintain Conversation Context

Slack threads are a natural mapping for Claude's conversation turns. Store message history keyed by thread_ts, and pass the full history as the messages array on each call so Claude has context from earlier in the thread. A simple in-memory map works for prototypes; for production, use Redis or a database keyed by channel + thread timestamp, with a TTL to expire stale threads.

Step 4: Stream for Long Responses

If your bot handles long-form questions (summaries, code generation, document Q&A), streaming avoids the user staring at a blank thread for 10+ seconds. Slack doesn't support true token-by-token streaming in a message, but you can simulate progressive updates by editing the message with chat.update every few hundred milliseconds as tokens arrive. See /docs/streaming for details on consuming server-sent events from the Claude API.

Step 5: Handle Tool Use for Richer Bots

If your bot needs to look something up — a ticket status, a database record, an internal doc — Claude's tool use feature lets you define functions the model can call mid-response. This is far more reliable than prompt-engineering the bot to "pretend" to fetch data. Define a tool schema (name, description, JSON input schema), pass it in the tools array, and handle the tool_use content block by executing your function and returning the result. See /docs/tools for the full request/response shape.

Managing API Keys Across a Slack Bot Team

If more than one person maintains the bot — a common scenario once it moves past a hackathon prototype — sharing a single Anthropic API key becomes a liability. There's no per-developer visibility into which key is burning budget, and rotating it means redeploying every environment at once.

This is the gap SubToAPI fills. Instead of one shared Anthropic key, each team member or environment (staging bot, production bot, dev sandbox) gets its own sub_live_... key, with usage and cost broken out per key in one dashboard. You keep the same request format (/v1/messages), so the bot code above doesn't change — you just swap the key and base URL. Plans start at Solo for a single bot maintainer, with Team and Scale tiers adding per-seat access for larger engineering teams. Check /pricing for details, or start with the free trial at /signup.

Common Pitfalls

Questions

Do I need a special Claude API plan for Slack bots? No — any Claude API access works. The integration is just an HTTP call from your bot server to the Messages endpoint. See /docs/quickstart for the minimal request shape.

Can Claude read Slack file uploads or images? Yes, if you pass the file content as a base64-encoded image block in the message content array. You'll need to download the file from Slack's API first since Slack URLs require bot token auth.

How do I avoid duplicate replies from Slack retries? Acknowledge the webhook with a 200 response immediately, before calling Claude, and deduplicate incoming events using event_ts or client_msg_id to ignore repeat deliveries.

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 →