slack-sdk review
slack-sdk 3.43.0 is Slack's official Python toolkit for Web API methods, incoming webhooks, request signatures, Socket Mode, OAuth, Grid-style Block Kit models, SCIM, and audit logs. WebClient maps methods such as chat.postMessage to Python calls, while Bolt remains the higher-level framework for routing events and interactive actions. Version 3.43.0 rejects empty signing secrets, raises the async aiohttp floor to 3.13.5, exposes ChatStream.ts, and makes thread_ts optional for assistant suggested prompts.
slack-sdk 3.43.0 installed in 0.3 seconds and used 3 MB in our sandbox, but import slack failed because aiohttp was absent, so verify your chosen sync or async import before deployment. Install it for several Slack APIs or signature helpers; a single outgoing notification is simpler through an incoming webhook.
We installed it
| Install | ✓ · 0.3s | 1 package on disk · 3 MB |
| Import | ✗ | import slack · pure Python · py.typed · requires Python >=3.7 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does slack-sdk install cleanly?
Yes. In a fresh container with an empty cache, pip install slack-sdk finished in 0.3s, leaving 1 package and 3 MB on disk. pip-audit reported no known vulnerabilities.
What does slack-sdk need to run?
Python >=3.7, and nothing compiled: it is pure Python. In our run import slack failed, so it needs extra system packages, and the package ships py.typed for type checkers.
slack-sdk or slack-bolt: which should you use?
slack-bolt: Use it for routed events, commands, shortcuts, modals, and interactive actions. slack-sdk 3.43.0 installed in 0.3 seconds and used 3 MB in our sandbox, but import slack failed because aiohttp was absent, so verify your chosen sync or async import before deployment.
When should you not use slack-sdk?
The integration posts one fixed notification; an incoming webhook plus an existing HTTP client has less package and admin surface
Use it if
- A Python service calls several Slack Web API methods and should use Slack-maintained request and response handling
- An HTTP endpoint needs Slack timestamp and signing-signature verification
- A private service receives event envelopes through Socket Mode instead of a public callback URL
- A multi-workspace app needs OAuth installation helpers and token storage interfaces
- The integration posts one fixed notification; an incoming webhook plus an existing HTTP client has less package and admin surface
- You are building handlers for events, slash commands, shortcuts, or modals; Slack Bolt supplies routing and middleware on top of this SDK
- You expect every import path to work from the base install; our import slack probe failed because aiohttp was missing
- You assume 429 responses retry automatically; the WebClient needs an explicit rate-limit retry handler
- Your team cannot own bot scopes, channel membership, app reinstallations, token rotation, and the distinction between xoxb and xapp tokens
Setup reality
We installed slack-sdk 3.43.0 in 0.3 seconds on Python 3.12. One package occupied 3 MB, pip-audit reported 0 known vulnerabilities, and the pure-Python distribution included py.typed. It requires Python 3.7 or newer, uses the MIT license, and declares 8 direct dependencies. Our import slack probe failed with ModuleNotFoundError: No module named 'aiohttp'. Test the exact import path your application uses.
Slack administration takes more work than pip. Create an app, choose bot scopes, install it to the workspace, keep the xoxb token in a secret store, and invite the bot to private channels it needs. A later scope change requires another installation approval. Version 3.43.0 also makes a non-empty signing secret mandatory for inbound request verification.
WebClient does not attach rate-limit retries by default. Add RateLimitErrorRetryHandler with a bounded count so a 429 respects Retry-After. List APIs use cursor pagination; iterating SlackResponse can fetch later pages, while reading only the first response can silently omit results in a large workspace. Store channel and user IDs because display names can change.
AsyncWebClient needs aiohttp, whose minimum supported version moved to 3.13.5 in this release. Socket Mode uses an xapp app token in addition to the xoxb bot token and expects prompt envelope acknowledgements. Signature verification must see the raw request bytes before JSON or form parsing changes them. Use files_upload_v2 for new upload code because Slack retired the older files.upload flow.
Patterns
Send a bot message post-message
import os
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
client = WebClient(token=os.environ['SLACK_BOT_TOKEN'])
try:
response = client.chat_postMessage(
channel='C0123456789', text='Deploy finished',
)
print(response['ts'])
except SlackApiError as error:
print(error.response['error'], error.response.status_code)Use a channel ID. Keep text even when blocks are supplied because notifications and accessibility clients use that fallback.
Handle Slack error codes branch-api-errors
try:
client.chat_postMessage(channel=channel_id, text=body)
except SlackApiError as error:
code = error.response['error']
if code == 'missing_scope':
needed = error.response.get('needed')
raise RuntimeError(f'reinstall with scope: {needed}')
if code == 'not_in_channel':
client.conversations_join(channel=channel_id)
client.chat_postMessage(channel=channel_id, text=body)SlackApiError includes the API code and HTTP status. Adding a missing scope requires the workspace to approve installation again.
Honor Retry-After on 429 retry-rate-limit
from slack_sdk import WebClient
from slack_sdk.http_retry.builtin_handlers import RateLimitErrorRetryHandler
client = WebClient(token=token)
client.retry_handlers.append(
RateLimitErrorRetryHandler(max_retry_count=3),
)The retry handler is not attached by default. Keep its count finite because each Retry-After wait extends request latency.
Follow every result cursor paginate-channels
channels = []
for page in client.conversations_list(limit=200, exclude_archived=True):
channels.extend(page['channels'])Iterating SlackResponse fetches subsequent cursors. Looking only at the first page truncates a workspace with more than the requested limit.
Use the current file upload flow upload-file
response = client.files_upload_v2(
channel='C0123456789',
file='./report.csv',
title='Nightly report',
initial_comment='Numbers are attached',
)
print(response['file']['id'])files_upload_v2 replaces the retired files.upload method and needs files:write permission.
Validate an inbound Slack request verify-signature
import os
from slack_sdk.signature import SignatureVerifier
verifier = SignatureVerifier(os.environ['SLACK_SIGNING_SECRET'])
def handle(request):
if not verifier.is_valid_request(
request.get_data(), dict(request.headers),
):
return 'invalid signature', 401
return process(request)Version 3.43.0 requires a non-empty secret. Pass the exact raw body so parsing does not change the signed bytes.
Post through an incoming webhook send-webhook
import os
from slack_sdk.webhook import WebhookClient
webhook = WebhookClient(os.environ['SLACK_WEBHOOK_URL'])
response = webhook.send(text='Build #482 passed')
assert response.status_code == 200 and response.body == 'ok'WebhookClient returns an HTTP status and body, unlike the JSON-like SlackResponse returned by WebClient.
Construct a Block Kit message build-blocks
from slack_sdk.models.blocks import ActionsBlock, ButtonElement, SectionBlock
blocks = [
SectionBlock(text='*Deploy 4.2.0* is ready'),
ActionsBlock(elements=[
ButtonElement(
text='Ship it', action_id='ship', value='4.2.0', style='primary',
),
]),
]
client.chat_postMessage(
channel=channel_id, blocks=blocks, text='Deploy 4.2.0 is ready',
)The model classes check payload structure locally. Plain dictionaries are also accepted when copied from Block Kit Builder.
Call Slack from asyncio post-async
import asyncio
import os
from slack_sdk.web.async_client import AsyncWebClient
client = AsyncWebClient(token=os.environ['SLACK_BOT_TOKEN'])
async def notify(channels, text):
await asyncio.gather(*(
client.chat_postMessage(channel=channel, text=text)
for channel in channels
))AsyncWebClient needs aiohttp 3.13.5 or newer for slack-sdk 3.43.0. Bound concurrency because a large gather can hit method limits.
Acknowledge Socket Mode envelopes receive-socket-mode
from slack_sdk.socket_mode import SocketModeClient
from slack_sdk.socket_mode.response import SocketModeResponse
socket = SocketModeClient(
app_token=os.environ['SLACK_APP_TOKEN'], web_client=client,
)
def receive(active_client, request):
active_client.send_socket_mode_response(
SocketModeResponse(envelope_id=request.envelope_id),
)
if request.type == 'events_api':
handle_event(request.payload['event'])
socket.socket_mode_request_listeners.append(receive)
socket.connect()Acknowledge before slow processing. The xapp app token opens Socket Mode, while the xoxb bot token authorizes Web API calls.
Resolve a Slack user by email lookup-email
def slack_id_for(email):
try:
return client.users_lookupByEmail(email=email)['user']['id']
except SlackApiError as error:
if error.response['error'] == 'users_not_found':
return None
raiseusers:read.email is a separate scope from users:read. Use this endpoint instead of downloading every user to search locally.
Post and update a threaded status reply-thread
parent = client.chat_postMessage(
channel=channel_id, text='Running migration...',
)
client.chat_postMessage(
channel=channel_id, thread_ts=parent['ts'], text='Step 1/3 done',
)
client.chat_update(
channel=channel_id, ts=parent['ts'], text='Migration complete',
)thread_ts points to the top-level message. chat_update also needs the original message's channel and timestamp.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| slack-bolt | PyPI | Use it for routed events, commands, shortcuts, modals, and interactive actions |
| httpx | PyPI | Use it when one incoming webhook request is the entire Slack integration |
| slackclient | PyPI | Keep it only while migrating legacy code; the project README says slack-sdk is its successor |
More web backend guides
urllib3 · requests · ws · anyio · httpx · undici · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

