> ## Documentation Index
> Fetch the complete documentation index at: https://docs.timbal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Slack Bot

> Build a Slack bot that responds in real time via webhooks, with an AI agent and threaded conversations

This guide shows how to create a Slack bot using Timbal that can:

* Respond to messages in real-time via webhooks
* Process user requests intelligently with AI agents
* Handle threaded conversations

## Setup Requirements

<AccordionGroup>
  <Accordion title="Installing Packages">
    Install the Slack SDK:

    ```bash theme={"dark"}
    pip install slack-sdk
    ```

    <Note>
      For all available Slack API methods and capabilities, see the [Slack API Methods Reference](https://docs.slack.dev/reference/methods).
    </Note>
  </Accordion>

  <Accordion title="Creating a Slack App">
    <Steps>
      <Step title="Create a Slack App">
        1. Go to [Slack API](https://api.slack.com/apps) and click **Create New App**
        2. Choose **From scratch**
        3. Enter your app name and select your workspace
      </Step>

      <Step title="Configure Bot Token Scopes">
        1. Go to **OAuth & Permissions**
        2. Add the following **Bot Token Scopes** based on Timbal handlers you'll use:

        ```
        # Message Operations
        chat:write            # Send messages
        chat:write.public     # Send messages to channels bot isn't in
        im:write             # Send direct messages

        # Channel & Conversation Operations
        channels:read        # View basic channel information
        channels:history     # Get messages from channels
        groups:read          # View basic private channel information
        im:read              # View basic direct message information
        im:history           # Get direct message history

        # App Mentions & Reactions
        app_mentions:read    # Receive app mention events
        reactions:read       # Read message reactions

        # File Operations
        files:read           # Download files from Slack
        files:write          # Upload files to Slack
        ```

        <Note>
          After adding or modifying scopes, you'll need to reinstall the app to your workspace for the changes to take effect.
        </Note>
      </Step>

      <Step title="Install App to Workspace">
        1. In **OAuth & Permissions**, click **Install to Workspace**
        2. Copy the **Bot User OAuth Token** (starts with `xoxb-`)
        3. Set as environment variable: `SLACK_BOT_TOKEN`
      </Step>

      <Step title="Enable Home Tab (for Direct Messages)">
        1. Go to **App Home** in your Slack app settings
        2. Under **Show Tabs**, enable **Home Tab**
        3. Check **Allow users to send Slash commands and messages from the messages tab**
        4. This enables users to send direct messages to your bot
      </Step>

      <Step title="Get Bot User ID">
        1. In your Slack workspace, start a direct message with your bot
        2. Click on the bot's name at the top of the chat
        3. In the profile panel, find the **Member ID** (format: `U12345678`)
        4. Set this as environment variable: `SLACK_BOT_USER_ID=U12345678`

        <Note>
          The Bot User ID is required to prevent infinite loops by filtering out the bot's own messages.
        </Note>
      </Step>
    </Steps>
  </Accordion>
</AccordionGroup>

## Implementation

### Setting up the Slack Bot Configuration

First, set up the Slack configuration with your bot's user ID and authentication token.

Create a `.env` file in your project root:

```bash .env theme={"dark"}
SLACK_BOT_USER_ID=U12345678  # Your actual bot user ID
SLACK_BOT_TOKEN=xoxb-your-bot-token-here  # Your bot token
```

Then configure your Python code:

```python theme={"dark"}
import os

from dotenv import load_dotenv
from slack_sdk import WebClient

# Load environment variables from .env
load_dotenv()

SLACK_BOT_USER_ID = os.getenv("SLACK_BOT_USER_ID")

client = WebClient(token=os.getenv("SLACK_BOT_TOKEN"))
```

### Creating the Agent

Create the Timbal agent with pre and post hooks for Slack integration:

```python theme={"dark"}
from timbal import Agent

agent = Agent(
    name="SlackAgent",
    system_prompt="You are a tech support AI assistant. Help users with their technical questions clearly and concisely.",
    model="openai/gpt-4.1-mini",
    pre_hook=pre_hook,
    post_hook=post_hook,
)
```

### Pre-hook: Processing Incoming Messages

The pre-hook handles incoming Slack webhook events and extracts relevant information.

<Note>
  **Parameter Name Flexibility**: The `_webhook` parameter name is completely custom. You can use any name you prefer:

  * `await agent(slack_data=body).collect()`
  * `await agent(webhook_payload=body).collect()`
  * `await agent(event_data=body).collect()`

  Just ensure the same name is used consistently in both the agent call and the pre\_hook check.
</Note>

```python theme={"dark"}
from timbal.state import get_run_context
from timbal.errors import bail

async def pre_hook():
    """Process incoming Slack webhook events before agent execution."""
    span = get_run_context().current_span()

    # Check if this is a Slack webhook event
    # Note: "_webhook" matches the parameter name used when calling agent(_webhook=body)
    if "_webhook" not in span.input:
        return  # Not triggered by Slack

    slack_event = span.input["_webhook"]["event"]

    # Ignore bot's own messages to prevent infinite loops
    if slack_event.get("user") == SLACK_BOT_USER_ID:
        raise bail()

    # Extract and validate message text
    text = slack_event.get("text", "")
    if not text:
        raise bail()

    # Store Slack context for response
    span.slack_channel = slack_event["channel"]
    span.slack_thread_ts = slack_event.get("thread_ts")  # Reply in same thread
    span.input["prompt"] = text
```

### Post-hook: Sending Responses

The post-hook sends the agent's response back to Slack:

```python theme={"dark"}
from timbal.state import get_run_context

def post_hook():
    """Send agent response back to Slack channel."""
    span = get_run_context().current_span()
    
    # Only process Slack webhook events
    if "_webhook" not in span.input:
        return

    # Extract response context
    slack_channel = span.slack_channel
    slack_thread_ts = span.slack_thread_ts
    reply = span.output.collect_text()

    # Send response if not empty
    if reply.strip():
        client.chat_postMessage(
            channel=slack_channel,
            text=reply,
            thread_ts=slack_thread_ts,
        )
```

### Complete Example

Here's the full implementation ready to use:

```python agent.py theme={"dark"}
import os

from dotenv import load_dotenv
from slack_sdk import WebClient
from timbal import Agent
from timbal.errors import bail
from timbal.state import get_run_context

# Load environment variables from .env
load_dotenv()

SLACK_BOT_USER_ID = os.getenv("SLACK_BOT_USER_ID")

client = WebClient(token=os.getenv("SLACK_BOT_TOKEN"))

async def pre_hook():
    """Process incoming Slack webhook events before agent execution."""
    span = get_run_context().current_span()

    # Only process Slack webhook events
    # Note: "_webhook" must match the parameter name used when calling agent(_webhook=body)
    if "_webhook" not in span.input:
        return

    slack_event = span.input["_webhook"]["event"]

    # Prevent infinite loops by ignoring bot's own messages
    if slack_event.get("user") == SLACK_BOT_USER_ID:
        raise bail()

    # Extract message text
    text = slack_event.get("text", "")
    if not text:
        raise bail()

    # Store context for response
    span.slack_channel = slack_event["channel"]
    span.slack_thread_ts = slack_event.get("thread_ts")
    span.input["prompt"] = text

def post_hook():
    """Send agent response back to Slack."""
    span = get_run_context().current_span()
    
    if "_webhook" not in span.input:
        return

    # Get response and send to Slack
    reply = span.output.collect_text()
    slack_channel = span.slack_channel
    slack_thread_ts = span.slack_thread_ts
    
    if reply.strip():
        client.chat_postMessage(
            channel=slack_channel,
            text=reply,
            thread_ts=slack_thread_ts,
        )

# Create the Slack agent
agent = Agent(
    name="SlackAgent",
    system_prompt="You are a tech support AI assistant. Help users with their technical questions clearly and concisely.",
    model="openai/gpt-4.1-mini",
    pre_hook=pre_hook,
    post_hook=post_hook,
)
```

## Running the Agent

Timbal provides built-in ngrok integration for easy local development. To use this feature:

<Steps>
  <Step title="Set up ngrok account and authtoken">
    1. Create an account at [ngrok.com](https://ngrok.com/)
    2. Install ngrok on your system and the Python package:

    ```bash theme={"dark"}
    pip install pyngrok
    ```

    3. Configure your authtoken:

    ```bash theme={"dark"}
    ngrok config add-authtoken YOUR_AUTHTOKEN
    ```
  </Step>

  <Step title="Create FastAPI Webhook Handler">
    Create a simple FastAPI server to handle Slack's URL verification and webhook events:

    ```python server.py theme={"dark"}
    import uvicorn
    from dotenv import load_dotenv
    from fastapi import FastAPI, Request
    from pyngrok import ngrok

    # Import our Slack agent to handle webhook events
    from agent import agent

    load_dotenv()

    app = FastAPI()

    @app.post("/")
    async def slack_events(request: Request):
        body = await request.json()
        
        if body.get("type") == "url_verification":
            return {"challenge": body.get("challenge")}
        
        # Process actual Slack events here
        if body.get("type") == "event_callback":
            await agent(_webhook=body).collect()  # _webhook is a custom parameter name you define
            
        return {"status": "ok"}

    if __name__ == "__main__":
        port = 8000
        public_url = ngrok.connect(port, "http")
        print(f"Public URL: {public_url}")
        
        uvicorn.run(app, host="0.0.0.0", port=port)
    ```
  </Step>

  <Step title="Run the Server and Get ngrok URL">
    1. Run the script to start your Slack bot:

    ```bash theme={"dark"}
    python server.py
    ```

    Or using uv (recommended):

    ```bash theme={"dark"}
    uv run server.py
    ```

    2. Copy the ngrok public URL displayed in the terminal (e.g., `https://abc123.ngrok-free.app`)
  </Step>

  <Step title="Enable Event Subscriptions">
    1. Go to **Event Subscriptions** and toggle **Enable Events**
    2. Set **Request URL** to our webhook endpoint (e.g., `https://abc123.ngrok-free.app/`)
    3. Subscribe to **Bot Events** - these determine what events Slack will send to your bot:

    ```
    message.channels  # Bot receives messages in public channels it's added to
    message.groups    # Bot receives messages in private channels it's added to
    message.im        # Bot receives direct messages sent to it
    app_mention       # Bot receives messages that mention it (@botname)
    reaction_added    # Bot receives events when reactions are added to messages
    ```
  </Step>
</Steps>

Your Slack bot is now ready! 🚀 Start a direct message with your bot or mention it in a channel to begin chatting with your AI assistant.

## Key Features

* **Real-time Responses**: Instantly processes Slack messages via webhooks
* **Thread Support**: Maintains conversation context in Slack threads
* **Loop Prevention**: Automatically ignores bot's own messages
* **Flexible AI**: Uses any LLM model supported by Timbal
* **Easy Integration**: Minimal setup with existing Slack handlers

## Troubleshooting

<AccordionGroup>
  <Accordion title="Bot Not Responding">
    * Check webhook URL is publicly accessible and returns 200 status
    * Ensure bot has correct permissions in Slack workspace
    * Confirm `SLACK_BOT_USER_ID` matches your bot's actual user ID
    * Test webhook endpoint manually with curl or Postman
  </Accordion>

  <Accordion title="Infinite Loop Issues">
    * Double-check `SLACK_BOT_USER_ID` is correct (found in Slack app settings)
    * Ensure pre-hook properly filters bot messages with `bail()`
    * Verify webhook events aren't being duplicated
    * Check that bot doesn't respond to its own message events
  </Accordion>

  <Accordion title="Agent Not Processing Messages">
    * Check that OpenAI API key is valid and has sufficient credits
    * Verify agent model name is correct (e.g., `openai/gpt-4o-mini`)
    * Ensure pre-hook is setting `span.input["prompt"]` correctly
    * Add logging to debug webhook payload structure
  </Accordion>

  <Accordion title="Server Won't Start">
    * Ensure all required packages are installed (`slack-sdk fastapi uvicorn pyngrok python-dotenv`)
    * Verify ngrok is installed and configured with authtoken
    * Make sure port 8000 is not already in use by another process
    * Ensure Slack Request URL matches your POST endpoint route with "/" (e.g., `https://abc123.ngrok-free.app/`)
  </Accordion>
</AccordionGroup>
