Create the Timbal agent with pre and post hooks for Slack integration:
from timbal import Agentagent = 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,)
The pre-hook handles incoming Slack webhook events and extracts relevant information.
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.
from timbal.state import get_run_contextfrom timbal.errors import bailasync 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
The post-hook sends the agent’s response back to Slack:
from timbal.state import get_run_contextdef 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, )
import osfrom dotenv import load_dotenvfrom slack_sdk import WebClientfrom timbal import Agentfrom timbal.errors import bailfrom timbal.state import get_run_context# Load environment variables from .envload_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"] = textdef 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 agentagent = 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,)
Install ngrok on your system and the Python package:
pip install pyngrok
Configure your authtoken:
ngrok config add-authtoken YOUR_AUTHTOKEN
2
Create FastAPI Webhook Handler
Create a simple FastAPI server to handle Slack’s URL verification and webhook events:
server.py
import uvicornfrom dotenv import load_dotenvfrom fastapi import FastAPI, Requestfrom pyngrok import ngrok# Import our Slack agent to handle webhook eventsfrom agent import agentload_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)
3
Run the Server and Get ngrok URL
Run the script to start your Slack bot:
python server.py
Or using uv (recommended):
uv run server.py
Copy the ngrok public URL displayed in the terminal (e.g., https://abc123.ngrok-free.app)
4
Enable Event Subscriptions
Go to Event Subscriptions and toggle Enable Events
Set Request URL to our webhook endpoint (e.g., https://abc123.ngrok-free.app/)
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 tomessage.groups # Bot receives messages in private channels it's added tomessage.im # Bot receives direct messages sent to itapp_mention # Bot receives messages that mention it (@botname)reaction_added # Bot receives events when reactions are added to messages
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.