Build Slack apps with the Bolt framework across Python, JavaScript, and Java — Block Kit UIs, interactive components, slash commands, event handling, OAuth installation flows, and Workflow Builder integration. USE WHEN building or productionizing a Slack app/bot, adding slash commands or interactive Block Kit UI, or wiring OAuth install flows.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Build Slack apps with the Bolt framework across Python, JavaScript, and Java — Block Kit UIs, interactive components, slash commands, event handling, OAuth installation flows, and Workflow Builder integration. USE WHEN building or productionizing a Slack app/bot, adding slash commands or interactive Block Kit UI, or wiring OAuth install flows.
cluster
python-backend
version
1.0.0
origin
antigravity-awesome-skills (MIT)
risk
unknown
source
vibeship-spawner-skills (Apache 2.0)
date_added
"2026-02-27T00:00:00.000Z"
Slack Bot Builder
Build Slack apps using the Bolt framework across Python, JavaScript, and Java.
Covers Block Kit for rich UIs, interactive components, slash commands,
event handling, OAuth installation flows, and Workflow Builder integration.
Focus on best practices for production-ready Slack apps.
Patterns
Bolt App Foundation Pattern
The Bolt framework is Slack's recommended approach for building apps.
It handles authentication, event routing, request verification, and
HTTP request processing so you can focus on app logic.
Key benefits:
Event handling in a few lines of code
Security checks and payload validation built-in
Organized, consistent patterns
Works for experiments and production
Available in: Python, JavaScript (Node.js), Java
When to use: Starting any new Slack app,Migrating from legacy Slack APIs,Building production Slack integrations
Python Bolt App
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import os
@app.message("hello")
def handle_hello(message, say):
"""Respond to messages containing 'hello'."""
user = message["user"]
say(f"Hey there <@{user}>!")
# Get context from the action
user = body["user"]["id"]
action_value = body["actions"][0]["value"]
# Update the message to remove interactive elements
# (Best practice: prevent double-clicks)
client.chat_update(
channel=body["channel"]["id"],
ts=body["message"]["ts"],
text=f"Approved by <@{user}>",
blocks=[] # Remove interactive blocks
)
Listen for app_home_opened events
@app.event("app_home_opened")
def update_home_tab(client, event):
"""Update the Home tab when user opens it."""
client.views_publish(
user_id=event["user"],
view={
"type": "home",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Welcome to the Ticket Bot!"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {"type": "plain_text", "text": "Create Ticket"},
"action_id": "create_ticket_button"
}
]
}
]
}
)
Socket Mode for development (no public URL needed)
if name == "main":
handler = SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
handler.start()
For production, use HTTP mode with a web server
from flask import Flask, request
from slack_bolt.adapter.flask import SlackRequestHandler
Block Kit is Slack's UI framework for building rich, interactive messages.
Compose messages using blocks (sections, actions, inputs) and elements
(buttons, menus, text inputs).
incident_id = body["actions"][0]["value"]
user = body["user"]["id"]
# Update your system
acknowledge_incident(incident_id, user)
# Update message to show acknowledgment
original_blocks = body["message"]["blocks"]
# Add acknowledgment to context
original_blocks[-1]["elements"].append({
"type": "mrkdwn",
"text": f":white_check_mark: Acknowledged by <@{user}>"
})
# Remove acknowledge button (prevent double-click)
action_block = next(b for b in original_blocks if b.get("block_id", "").startswith("incident_actions"))
action_block["elements"] = [e for e in action_block["elements"] if e["action_id"] != "acknowledge_incident"]
client.chat_update(
channel=body["channel"]["id"],
ts=body["message"]["ts"],
blocks=original_blocks
)
Hardcoding action_ids (use dynamic IDs when needed)
Not handling button clicks idempotently
OAuth Installation Pattern
Enable users to install your app in their workspaces via OAuth 2.0.
Bolt handles most of the OAuth flow, but you need to configure it
and store tokens securely.
70% of users abandon installation when confronted with excessive
permission requests - request only what you need!
When to use: Distributing app to multiple workspaces,Building public Slack apps,Enterprise-grade integrations
from slack_bolt import App
from slack_bolt.oauth.oauth_settings import OAuthSettings
from slack_sdk.oauth.installation_store import FileInstallationStore
from slack_sdk.oauth.state_store import FileOAuthStateStore
import os
For production, use database-backed stores
For example: PostgreSQL, MongoDB, Redis
class DatabaseInstallationStore:
"""Store installation data in your database."""
async def save(self, installation):
"""Save installation when user completes OAuth."""
await db.installations.upsert({
"team_id": installation.team_id,
"enterprise_id": installation.enterprise_id,
"bot_token": encrypt(installation.bot_token),
"bot_user_id": installation.bot_user_id,
"bot_scopes": installation.bot_scopes,
"user_id": installation.user_id,
"installed_at": installation.installed_at
})
async def find_installation(self, *, enterprise_id, team_id, user_id=None, is_enterprise_install=False):
"""Find installation for a workspace."""
record = await db.installations.find_one({
"team_id": team_id,
"enterprise_id": enterprise_id
})
if record:
return Installation(
bot_token=decrypt(record["bot_token"]),
# ... other fields
)
return None
# Send welcome message
app.client.chat_postMessage(
token=installation.bot_token,
channel=installation.user_id,
text="Thanks for installing! Type /help to get started."
)
return "Installation successful! You can close this window."
Situation: Handling slash commands, shortcuts, or interactive components
Symptoms:
User sees "This command timed out" or "Something went wrong."
The action never completes even though your code runs.
Works in development but fails in production.
Why this breaks:
Slack requires acknowledgment within 3 seconds for ALL interactive requests:
Slash commands
Button/select menu clicks
Modal submissions
Shortcuts
If you do ANY slow operation (database, API call, LLM) before responding,
you'll miss the window. Slack shows an error even if your bot eventually
processes the request correctly.
Recommended fix:
Acknowledge immediately, process later
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import threading
app = App(token=os.environ["SLACK_BOT_TOKEN"])
@app.command("/slow-task")defhandle_slow_task(ack, command, client, respond):
# ACK IMMEDIATELY - before any processing
ack("Processing your request...")
# Do slow work in backgrounddefdo_work():
result = call_slow_api(command["text"]) # Takes 10 seconds
respond(f"Done! Result: {result}")
threading.Thread(target=do_work).start()
@app.view("modal_submission")defhandle_modal(ack, body, client, view):
# ACK with response_action for modals
ack(response_action="clear") # Or "update" with new view# Process in background
user_id = body["user"]["id"]
values = view["state"]["values"]
# ... slow processing
For Bolt framework - use lazy listeners
# Bolt handles ack() automatically with lazy listeners@app.command("/slow-task")defhandle_slow_task(ack, command, respond):
ack() # Still call ack() first!@handle_slow_task.lazydefprocess_slow_task(command, respond):
# This runs after ack, can take as long as needed
result = slow_operation(command["text"])
respond(result)
Not Validating OAuth State Parameter (CSRF)
Severity: CRITICAL
Situation: Implementing OAuth installation flow
Symptoms:
Bot appears to work, but you're vulnerable to CSRF attacks.
Attackers could trick users into installing malicious configurations.
Why this breaks:
The OAuth state parameter prevents CSRF attacks. Flow:
You generate random state, store it, send to Slack
User authorizes in Slack
Slack redirects back with code + state
You MUST verify state matches what you stored
Without this, an attacker can craft a malicious OAuth URL and trick
admins into completing the flow with attacker's authorization code.
Recommended fix:
Proper state validation
import secrets
from flask import Flask, request, session, redirect
from slack_sdk.oauth import AuthorizeUrlGenerator
from slack_sdk.oauth.state_store import FileOAuthStateStore
app = Flask(__name__)
app.secret_key = os.environ["SESSION_SECRET"]
# Use Slack SDK's state store (Redis recommended for production)
state_store = FileOAuthStateStore(
expiration_seconds=300, # 5 minutes
base_dir="./oauth_states"
)
@app.route("/slack/install")definstall():
# Generate cryptographically secure state
state = state_store.issue()
# Store in session for verification
session["oauth_state"] = state
authorize_url = AuthorizeUrlGenerator(
client_id=os.environ["SLACK_CLIENT_ID"],
scopes=["channels:history", "chat:write"],
user_scopes=[]
).generate(state)
return redirect(authorize_url)
@app.route("/slack/oauth/callback")defoauth_callback():
# CRITICAL: Verify state
received_state = request.args.get("state")
stored_state = session.get("oauth_state")
ifnot received_state or received_state != stored_state:
return"Invalid state parameter - possible CSRF attack", 403# Also use state_store.consume() for one-time useifnot state_store.consume(received_state):
return"State already used or expired", 403# Now safe to exchange code for token
code = request.args.get("code")
# ... complete OAuth flow
Exposing Bot/User Tokens
Severity: CRITICAL
Situation: Storing or logging Slack tokens
Symptoms:
Unauthorized messages sent from your bot. Attackers reading private
channels. Token found in logs, git history, or client-side code.
Why this breaks:
Slack tokens provide FULL access to whatever scopes they have:
Bot tokens (xoxb-*): Access workspaces where installed
User tokens (xoxp-*): Access as that specific user
# BAD - never do this
client = WebClient(token="xoxb-12345-...")
# GOOD - environment variables
client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
# BAD - logging tokens
logger.error(f"API call failed with token {token}")
# GOOD - never log tokens
logger.error(f"API call failed for team {team_id}")
# BAD - sending token to frontendreturn {"token": bot_token}
# GOOD - only send what frontend needsreturn {"channels": channel_list}
1. Slack API > Your App > OAuth & Permissions
2. Click "Rotate" for the exposed token
3. Update all deployments immediately
4. Review Slack audit logs for unauthorized access
Requesting Unnecessary OAuth Scopes
Severity: HIGH
Situation: Configuring OAuth scopes for your app
Symptoms:
Users hesitate to install due to scary permission warnings.
Lower install rates. Security team blocks deployment.
App rejected from Slack App Directory.
Why this breaks:
Each OAuth scope grants specific permissions. Requesting more than
you need:
Makes install consent screen scary
Increases attack surface if token leaked
May violate enterprise security policies
Can get your app rejected from App Directory
Common over-requests:
admin when you just need chat:write
channels:read when you only message one channel
users:read.email when you don't need emails
Recommended fix:
Request minimum required scopes
# For a simple notification bot
MINIMAL_SCOPES = [
"chat:write", # Post messages"channels:join", # Join public channels (if needed)
]
# NOT NEEDED for basic notification:# - channels:read (unless you list channels)# - users:read (unless you look up users)# - channels:history (unless you read messages)# For a slash command bot
SLASH_COMMAND_SCOPES = [
"commands", # Register slash commands"chat:write", # Respond to commands
]
# For a bot that responds to mentions
MENTION_BOT_SCOPES = [
"app_mentions:read", # Receive @mentions"chat:write", # Reply to mentions
]
# Start with minimal scopes
INITIAL_SCOPES = ["chat:write", "commands"]
# Request additional scopes only when needed@app.command("/enable-reactions")defenable_reactions(ack, client, command):
ack()
# Check if we have the scope
auth_result = client.auth_test()
# If missing reactions:write, prompt re-authif needs_additional_scope:
# Send user to re-auth with additional scopepass
Exceeding Block Kit Limits
Severity: MEDIUM
Situation: Building complex message UIs with Block Kit
Symptoms:
Message fails to send with "invalid_blocks" error.
Modal won't open. Message truncated unexpectedly.
Why this breaks:
Block Kit has strict limits that aren't always obvious:
50 blocks per message/modal
3000 characters per text block
10 elements per actions block
100 options per select menu
Modal: 50 blocks, 24KB total
Home tab: 100 blocks
Exceeding these causes silent failures or cryptic errors.
from slack_bolt.adapter.socket_mode import SocketModeHandler
import time
classRobustSocketHandler:
def__init__(self, app, app_token):
self.app = app
self.app_token = app_token
self.handler = Nonedefstart(self):
whileTrue:
try:
self.handler = SocketModeHandler(self.app, self.app_token)
self.handler.start()
except Exception as e:
logger.error(f"Socket Mode disconnected: {e}")
time.sleep(5) # Backoff before reconnect
Not Verifying Request Signatures
Severity: CRITICAL
Situation: Receiving webhooks from Slack
Symptoms:
Attackers can send fake requests to your webhook endpoints.
Spoofed slash commands. Fake event notifications processed.
Why this breaks:
Slack signs all requests with X-Slack-Signature header using your
signing secret. Without verification, anyone who knows your webhook
URL can send fake requests.
This is different from OAuth tokens - signing verifies the REQUEST
came from Slack, not that you have permission to call Slack.
Recommended fix:
Bolt handles this automatically
from slack_bolt import App
# Bolt verifies signatures automatically when you provide signing_secret
app = App(
token=os.environ["SLACK_BOT_TOKEN"],
signing_secret=os.environ["SLACK_SIGNING_SECRET"]
)
# All requests to your handlers are verified