- Slack API アプリ作成〜Bot Token Scopes/Socket Mode の設定
- Claude Code による Bolt for Python 雛形生成と実装パターン
- メッセージ受信・返信、スラッシュコマンド、ボタン・モーダルの実装
- cron による定期投稿・外部 API 連携・トークンの安全管理
- よくあるエラー(スコープ不足・署名検証失敗・3秒ルール)の Claude Code 活用解決法
対象読者:Python または JavaScript を書いたことがある開発者・実務者。Slack ワークスペース管理者権限があるとスムーズ。
今日やること:記事の手順に沿って、動作する Slack bot を1本デプロイする。
ある日、チームの定例で「毎朝 KPI を Slack に流したい」「問い合わせフォームの受信を即通知したい」という要望が出た。ちょっとしたスクリプトで済む話なのに、Slack API の仕様を調べているうちに1〜2時間が消えた、という経験はないだろうか。
正直、Slack bot の開発は「小さな壁がいくつもある」のが面倒な理由だ。アプリ作成、スコープ設定、イベント購読、署名検証…それぞれは難しくないが、最初にまとめて把握するのにコストがかかる。Claude Code を相棒にすると、この「仕様の把握コスト」が大幅に下がる。
この記事では、Claude Code で Slack bot を実際にゼロから作った手順を、コードと Claude Code へのプロンプト例つきで解説する。2026年6月時点の Slack API(Bolt for Python v2.x)をベースにしている。

Slack アプリの作り方 — api.slack.com からはじめる
まず https://api.slack.com/apps にアクセスし、”Create New App” → “From scratch” でアプリを作成する。アプリ名とインストール先ワークスペースを選ぶだけで、アプリの管理ページが生成される。
Bot Token Scopes の設定
左メニューの OAuth & Permissions → Scopes の Bot Token Scopes に必要な権限を追加する。最初に入れておくべきスコープは次のとおり。
| スコープ | 用途 |
|---|---|
channels:history |
パブリックチャンネルのメッセージ読み取り |
chat:write |
メッセージ送信(必須) |
commands |
スラッシュコマンドの受信 |
im:history |
DM メッセージ読み取り |
im:write |
DM 送信 |
app_mentions:read |
@bot メンションの受信 |
reactions:write |
絵文字リアクションを付ける |
スコープを追加したら、ページ上部の “Install to Workspace” ボタンを押す。インストール後に Bot User OAuth Token(xoxb- で始まる文字列)が発行されるのでコピーしておく。
イベント購読 vs Socket Mode — どちらを選ぶか
Slack からのイベントを受け取る方法は 2 種類ある。
- HTTP (Events API):公開 URL(HTTPS)に POST で受け取る。本番サーバや AWS Lambda などサーバレスに向く。
- Socket Mode:ボットが WebSocket で Slack に接続し、ポーリング不要でイベントを受け取る。ローカル開発・社内 LAN のみのサーバに向く。公開 URL 不要。
個人開発・社内 bot の場合は Socket Mode が圧倒的に楽だ。公開エンドポイントの準備が不要で、ファイアウォール内でも動く。本番でも「社内専用で外部公開したくない」ケースはSocket Modeが有力な選択肢になる。
Socket Mode を使う場合は、アプリ管理ページの Socket Mode メニューを有効化し、App-Level Token(xapp- で始まる文字列)を生成しておく。スコープは connections:write を付ける。
Claude Code でプロジェクトを初期化する — Bolt for Python の雛形生成
ここから Claude Code を使う。まず空ディレクトリを作って開く。
mkdir slack-bot-project
cd slack-bot-project
claude
Claude Code のプロンプトに以下を投げる。
Bolt for Python を使って Slack bot の雛形を作って。要件:
– Python 3.11 想定
– Socket Mode(SLACK_APP_TOKEN環境変数)で接続
–SLACK_BOT_TOKENは環境変数から読む
–app.messageで “hello” に反応して返信するサンプルを含める
–.env.exampleとrequirements.txtも生成して
–.gitignoreに.envを含める
Claude Code が生成した app.py の骨格はだいたい次のような形になる。
import os
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
from dotenv import load_dotenv
load_dotenv()
app = App(token=os.environ["SLACK_BOT_TOKEN"])
@app.message("hello")
def handle_hello(message, say):
say(f"Hello <@{message['user']}>!")
if __name__ == "__main__":
handler = SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
handler.start()
requirements.txt には以下が必要だ(バージョンはインストール時に最新を確認すること)。
slack-bolt>=1.18.0
python-dotenv>=1.0.0
インストールして動作確認:
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
python app.py
“hello” とチャンネルに投稿すると、bot が返信してくれたら成功だ。
メッセージ受信と返信 — app.message と app.event の使い分け
app.message — テキストマッチ
app.message はメッセージ本文にパターン(文字列または正規表現)がマッチした時に発火する。最もシンプルなハンドラだ。
import re
@app.message("ヘルプ")
def handle_help(message, say):
say("コマンド一覧:\n- `/status` ステータス確認\n- `/report` 日次レポート")
@app.message(re.compile(r"バグ|エラー|障害"))
def handle_bug_report(message, say, client):
# スレッドに返信
client.chat_postMessage(
channel=message["channel"],
thread_ts=message["ts"],
text=f"<@{message['user']}> 状況を教えてください。エラーログを貼ってもらえると助かります。"
)
app.event — イベントタイプ指定
@メンション、DM、チャンネル参加など特定のイベントタイプを直接ハンドルしたい場合は app.event を使う。イベントタイプ名は Slack Events API のドキュメント(docs.slack.dev/apis/events-api)で確認できる。
@app.event("app_mention")
def handle_mention(event, say):
user = event["user"]
text = event.get("text", "")
say(f"<@{user}> メンションありがとう! 「{text}」と書いてくれたね。")
@app.event("message")
def handle_all_messages(event, logger):
# 全メッセージをログに記録(返信は不要な場合)
logger.info(f"Message received: {event.get('text', '')}")
重要な点として、イベントを受け取ったら 3 秒以内に ACK(応答)を返さなければならない。Bolt フレームワークは say() を呼ぶか、あるいはハンドラが正常に return した時点で自動的に HTTP 200 を返すので、通常は意識しなくてよい。ただし外部 API 呼び出しなど時間のかかる処理は非同期で行う必要がある(後述)。
Claude Code による実装支援の活用例
今の
app.pyに以下を追加して:
1.app_mentionイベントで @メンションを受け取り、メッセージ本文の内容を ChatGPT API(openai ライブラリ)に渡して返信を生成する
2. 処理が長くなるのでsay()の前に「考え中…」をリアクション(👀)で付けて、完了後に外す
3. OPENAI_API_KEY は環境変数で管理
既存のハンドラとの衝突に注意して
このようなプロンプトを投げると、Claude Code は client.reactions_add、client.reactions_remove、async 処理の組み合わせまで一気に書いてくれる。「既存のハンドラとの衝突に注意して」という一文を入れておくと、@app.event("message") と @app.message の二重ハンドラ問題も避けてくれることが多い。
スラッシュコマンドとインタラクティブ — ボタン・モーダルの実装
スラッシュコマンドの登録
アプリ管理ページの Slash Commands メニューで “Create New Command” をクリック。コマンド名(例:/status)とリクエスト URL(HTTP モードの場合)または「Socket Mode で受け取る」設定を行う。Socket Mode の場合はリクエスト URL に任意の文字列を入れても動作する(Slack 側でバリデーションしない)。
コードでは次のように受け取る。
@app.command("/status")
def handle_status(ack, respond, command):
ack() # まず ACK を返す(必須・3秒以内)
user = command["user_id"]
channel = command["channel_id"]
text = command.get("text", "").strip()
# 処理
respond(f"ステータスを確認中... チャンネル: <#{channel}>, 引数: {text}")
ack() は最初に必ず呼ぶ。これを忘れると “This action cannot be completed” というエラーが Slack 上で表示される(よくある詰まりポイント)。
ボタン付きメッセージ(Block Kit)
Block Kit を使うとリッチなメッセージを送れる。承認フローや選択肢提示によく使う。
@app.command("/approve")
def handle_approve_command(ack, client, command, body):
ack()
client.chat_postMessage(
channel=command["channel_id"],
blocks=[
{
"type": "section",
"text": {"type": "mrkdwn", "text": f"*申請が届きました*\n送信者: <@{command['user_id']}>\n内容: {command.get('text', '(なし)')}"}
},
{
"type": "actions",
"block_id": "approve_actions",
"elements": [
{
"type": "button",
"text": {"type": "plain_text", "text": "✅ 承認"},
"style": "primary",
"action_id": "approve_btn",
"value": command["user_id"]
},
{
"type": "button",
"text": {"type": "plain_text", "text": "❌ 却下"},
"style": "danger",
"action_id": "reject_btn",
"value": command["user_id"]
}
]
}
]
)
ボタンのクリックイベントは app.action で受け取る。
@app.action("approve_btn")
def handle_approve(ack, body, client, action):
ack()
requester = action["value"]
approver = body["user"]["id"]
# メッセージを更新してボタンを非表示に
client.chat_update(
channel=body["channel"]["id"],
ts=body["message"]["ts"],
text=f"<@{approver}> が承認しました ✅",
blocks=[]
)
# 申請者に DM で通知
client.chat_postMessage(
channel=requester,
text=f"あなたの申請が <@{approver}> に承認されました。"
)
モーダル(views)
フォーム入力が必要な場合はモーダルを使う。views.open で開き、views.submission で送信を受け取る。
@app.command("/feedback")
def open_feedback_modal(ack, client, command, body):
ack()
client.views_open(
trigger_id=body["trigger_id"],
view={
"type": "modal",
"callback_id": "feedback_modal",
"title": {"type": "plain_text", "text": "フィードバック送信"},
"submit": {"type": "plain_text", "text": "送信"},
"close": {"type": "plain_text", "text": "キャンセル"},
"blocks": [
{
"type": "input",
"block_id": "feedback_block",
"label": {"type": "plain_text", "text": "内容"},
"element": {
"type": "plain_text_input",
"action_id": "feedback_input",
"multiline": True,
"placeholder": {"type": "plain_text", "text": "自由に書いてください"}
}
}
]
}
)
@app.view("feedback_modal")
def handle_feedback_submit(ack, body, view, client):
ack()
user = body["user"]["id"]
feedback = view["state"]["values"]["feedback_block"]["feedback_input"]["value"]
# 管理チャンネルに投稿
client.chat_postMessage(
channel="#feedback-log",
text=f"<@{user}> からフィードバック:\n{feedback}"
)
外部 API 連携と定期投稿 — cron・スケジューラの実装
外部 API 連携(非同期処理)
外部 API の呼び出しや DB クエリなど、時間のかかる処理を含むハンドラでは「3秒ルール」に注意が必要だ。Slack はハンドラから 3 秒以内にレスポンスを受け取れないと「タイムアウト」とみなして再試行する。
Bolt for Python では app.command や app.message ハンドラ内でデコレータを使わずに非同期を扱いたい場合、respond や say は後から呼んでも構わない(WebSocket/Socket Mode の場合は ACK だけ先に送れる)。
より確実な方法は Python の threading か、async 版の Bolt(slack_bolt.async_app.AsyncApp)を使うことだ。
import threading
import requests
@app.command("/report")
def handle_report(ack, respond, command):
ack() # まず即 ACK
def generate_report():
# 時間のかかる処理
data = requests.get("https://api.example.com/kpi").json()
text = f"*本日の KPI*\n売上: {data['revenue']:,} 円\nCV: {data['conversion']} 件"
respond(text)
thread = threading.Thread(target=generate_report)
thread.start()
定期投稿(cron)
毎朝 KPI を流す、週次レポートを送るなどの定期投稿は、Python の schedule ライブラリか APScheduler を使うのが定番だ。
import schedule
import time
import threading
def post_daily_summary():
"""毎朝 9 時に日次サマリを投稿"""
data = fetch_kpi_from_db() # DB or API から取得
app.client.chat_postMessage(
channel="#general",
blocks=[
{
"type": "header",
"text": {"type": "plain_text", "text": f"📊 日次レポート {data['date']}"}
},
{
"type": "section",
"fields": [
{"type": "mrkdwn", "text": f"*売上*\n{data['revenue']:,} 円"},
{"type": "mrkdwn", "text": f"*CV数*\n{data['conversion']} 件"},
{"type": "mrkdwn", "text": f"*アクティブユーザー*\n{data['dau']:,} 人"},
{"type": "mrkdwn", "text": f"*障害件数*\n{data['incidents']} 件"}
]
}
]
)
schedule.every().day.at("09:00").do(post_daily_summary)
def run_scheduler():
while True:
schedule.run_pending()
time.sleep(30)
# スケジューラを別スレッドで動かす
scheduler_thread = threading.Thread(target=run_scheduler, daemon=True)
scheduler_thread.start()
以下の仕様で定期投稿機能を追加して:
– 毎朝 9:00 JST に#kpi-dailyチャンネルへ Block Kit のカードで投稿
– データはhttps://api.example.com/kpi?date=YYYY-MM-DDから取得(requests)
– 取得失敗時は#alertsチャンネルにエラーを投稿してスキップ
– APScheduler を使い、タイムゾーンは Asia/Tokyo で指定
– 既存の Socket Mode ハンドラと同一プロセスで動かす
APScheduler を使う場合は pip install apscheduler が必要。タイムゾーン指定には pytz も入れておくと安心だ。
from apscheduler.schedulers.background import BackgroundScheduler
import pytz
scheduler = BackgroundScheduler(timezone=pytz.timezone("Asia/Tokyo"))
scheduler.add_job(post_daily_summary, "cron", hour=9, minute=0)
scheduler.start()
トークン・署名シークレットの安全管理
環境変数管理の基本
Bot Token(xoxb-)、App Token(xapp-)、Signing Secret はコードに直書きしない。.env ファイルに書き、.gitignore に追加するのが最低ライン。
# .env(git にコミットしない)
SLACK_BOT_TOKEN=xoxb-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxxxxxx
SLACK_APP_TOKEN=xapp-1-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxx
SLACK_SIGNING_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# アプリ側(Python)
import os
from dotenv import load_dotenv
load_dotenv()
BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"] # KeyError で即気づける
本番環境では .env ではなく OS の環境変数(systemd の EnvironmentFile、AWS の Secrets Manager、GCP の Secret Manager など)に移すのが望ましい。
リクエスト署名検証の仕組み
HTTP モードで Slack からのリクエストを受け取る場合、なりすましを防ぐための署名検証が必要だ。Slack は各リクエストに X-Slack-Request-Timestamp と X-Slack-Signature ヘッダを付ける。Bolt フレームワークを使っている限り、App(signing_secret=...) で初期化するだけで自動的に検証される。
# HTTP モードの場合
app = App(
token=os.environ["SLACK_BOT_TOKEN"],
signing_secret=os.environ["SLACK_SIGNING_SECRET"] # 自動で署名検証
)
署名検証が失敗する主な原因と対処:
- リプレイアタック防御:タイムスタンプが 5 分以上ずれていると Bolt が弾く。サーバの時計が狂っていないか確認。
- 生の Body が必要:
json.loads()で変換した後の body では署名が合わなくなる。Bolt は内部でこれを処理しているので、フレームワーク外で middleware を挟む場合は生 bytes を保持すること。 - Signing Secret の取り違え:複数のアプリを管理していると別アプリの Secret を使うミスがある。アプリ管理ページの Basic Information → App Credentials で確認。
デプロイ — Socket Mode 常駐 vs サーバ/サーバレス
Socket Mode 常駐プロセス
社内専用 bot の定番は、VPS や EC2 に常駐プロセスとして動かす方法だ。systemd で管理するのが安定している。
# /etc/systemd/system/slack-bot.service
[Unit]
Description=Slack Bot (Bolt for Python)
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/opt/slack-bot
EnvironmentFile=/opt/slack-bot/.env
ExecStart=/opt/slack-bot/.venv/bin/python app.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable slack-bot
sudo systemctl start slack-bot
sudo journalctl -u slack-bot -f # ログ確認
HTTP モード + サーバレス(AWS Lambda / Cloud Run)
HTTP モードは Slack からの POST を受け取るために公開エンドポイントが必要だが、AWS Lambda や GCP Cloud Run と組み合わせると低コストで運用できる。Bolt には SocketModeHandler の代わりに SlackRequestHandler(Lambda 用)や ASGI アダプタが用意されている。
# Lambda 用(Mangum + FastAPI + Bolt の例)
from slack_bolt.adapter.aws_lambda import SlackRequestHandler
def lambda_handler(event, context):
slack_handler = SlackRequestHandler(app=app)
return slack_handler.handle(event, context)
サーバレスでの注意点:コールドスタートが発生すると最初のレスポンスが 3 秒を超える可能性がある。Provisioned Concurrency(Lambda)や最小インスタンス数 1(Cloud Run)で対策する。
Claude Code でデプロイ設定を生成する
# Claude Code に投げるプロンプト例
# 「以下の要件で systemd unit ファイルと Dockerfile を生成して:
# - Python 3.11 slim イメージ
# - .env をビルドに含めず、実行時に --env-file で渡す
# - ヘルスチェックは /health エンドポイント(HTTP モード)
# - 非 root ユーザで実行」
よくあるエラーと Claude Code での直し方
エラー 1:スコープ不足(missing_scope)
アクションを実行した時に {"ok": false, "error": "missing_scope"} が返ってくる。エラーメッセージの needed フィールドにどのスコープが必要かが書いてある。
# エラーレスポンス例
{
"ok": false,
"error": "missing_scope",
"needed": "channels:history",
"provided": "chat:write"
}
対処:api.slack.com/apps → OAuth & Permissions → Scopes に channels:history を追加 → “Reinstall to Workspace”。Claude Code に「missing_scope エラーが出ている。needed: channels:history。スコープ追加の手順と、コード上の対処が必要か教えて」と聞くと、設定手順とコードの見直しポイントを一緒に教えてくれる。
エラー 2:署名検証失敗(invalid_signature)
HTTP モードで Slack からリクエストが来た時に署名が合わず 403 が返るパターン。主な原因は:
- Signing Secret が古い(再生成後に環境変数を更新し忘れた)
- リクエストボディをミドルウェアが変換していた
- サーバ時刻のずれ(
ntpdateで同期)
# デバッグ用に署名検証をスキップ(開発時のみ)
import os
os.environ["SLACK_IGNORE_BODY_HASH"] = "1" # 本番では絶対に使わないこと
エラー 3:3秒タイムアウト(dispatch_failed)
重い処理を同期実行するとタイムアウトする。Claude Code に次のように聞くと修正してもらえる。
handle_reportハンドラでdispatch_failedエラーが出る。外部 API 呼び出しに 5〜10 秒かかっているのが原因だと思う。ack()を先に返してバックグラウンドで処理する形にリファクタリングして。Socket Mode を使っているのでスレッドで大丈夫なはず。
エラー 4:cannot_dm_bot(bot への DM 不可)
Bot 同士の DM は Slack の仕様上できない。channel に bot の user ID が入っているケースで発生する。送信先が人間かどうかを users.info で確認する処理を入れると安全だ。
エラー 5:expired_trigger(モーダル trigger_id の期限切れ)
trigger_id は有効期限が 3 秒。スラッシュコマンドを受け取ってから 3 秒以内に views.open を呼ばないと期限切れになる。ack() 直後に views.open を呼ぶようにする。
実装のまとめと次のステップ
Claude Code を使った Slack bot 開発のポイントをまとめる。
- Slackアプリ作成:api.slack.com/apps でアプリを作り、Bot Token Scopes を最小限で設定する。Socket Mode は社内専用 bot に最適。
- 雛形生成:Claude Code にプロジェクト要件を一気に投げれば、
requirements.txt・.gitignore・.env.exampleまで揃う。 - ハンドラ設計:
app.message(テキストマッチ)とapp.event(イベントタイプ指定)を使い分ける。必ず 3 秒以内に ACK を返す設計を意識する。 - インタラクティブ:Block Kit のボタン・モーダルで、承認フロー・フォーム収集を実装できる。
- 定期投稿:APScheduler + JST タイムゾーン設定で、日次・週次レポートを自動化できる。
- セキュリティ:Token・Signing Secret は環境変数管理。HTTP モードは Bolt の署名検証を必ず有効にする。
- エラー対応:missing_scope・署名検証失敗・3 秒タイムアウトの 3 つを押さえておけば、大半の詰まりは解消できる。
Slack bot 開発と関連する発展トピックとして、バックエンド API の設計については Claude Code でバックエンド API を開発する実践ガイド も合わせて読んでほしい。また定期バッチや cron 自動化全般は Claude Code でバッチ・cron 自動化を実装する で詳しく解説している。
- api.slack.com/apps でアプリを作成し、Socket Mode を有効化する
- Claude Code に「Bolt for Python の雛形を作って」と投げ、まず hello world を動かす
- 自分のチームの定期レポート or 承認フローを 1 つ bot 化してみる
Slack bot 開発や Claude Code 活用でお困りの場合は、Uravation の無料相談からご連絡ください。社内ツール開発のスポットコンサルも対応しています。
FAQ
Q. Socket Mode と HTTP モード、どちらを使えばいいですか?
社内専用の bot で外部公開が不要な場合は Socket Mode を推奨します。サーバに固定 IP や公開ドメインがなくても動きます。外部ユーザが使う Slack アプリや AWS Lambda でホストする場合は HTTP モードが向いています。
Q. Bolt for Python と Bolt for JavaScript、どちらを選ぶべきですか?
チームの得意言語で選んで問題ありません。Python は ML ライブラリや外部 API との組み合わせに強く、JavaScript(Node.js)は非同期処理が標準で扱いやすいです。どちらも API 機能に差はありません。
Q. 3 秒ルールの具体的な対処法を教えてください。
ack() をハンドラの最初に呼んだ後、重い処理を threading.Thread で別スレッドに逃がすか、async 版の Bolt(AsyncApp)で await を使います。Lambda などサーバレス環境では Lazy Listeners(lazy=True)も有効です。
Q. Slack の Bot Token が漏れたらどうすればいいですか?
api.slack.com/apps のアプリ管理画面で “Regenerate” して即座に無効化します。Signing Secret も同様に再生成してください。GitHub などのリポジトリに誤ってコミットした場合は、Token の無効化後にコミット履歴からも削除してください(git filter-branch または git-filter-repo)。
Q. Claude Code はどのあたりが特に効果的でしたか?
Block Kit の JSON を書く作業が特に効果的でした。ブロック構造のネストが深く、手書きはミスしやすいですが、「承認ボタン付きの Section Block を作って」と投げるだけで正確な JSON が出てきます。また、エラーメッセージをそのまま貼って「このエラーの原因と修正方法を教えて」と聞くデバッグサイクルも非常にスムーズです。
株式会社Uravation 代表取締役。X(@SuguruKun_ai)フォロワー約10万人。100社以上の企業向けAI研修・導入支援を手がける。著書『AIエージェント仕事術』(SBクリエイティブ)。SoftBank IT連載7回執筆。Claude Code の業務活用・内製化支援を専門とする。
docs.slack.dev(Slack 公式開発者ドキュメント) /
docs.slack.dev/tools/bolt-python(Bolt for Python 公式) /
docs.slack.dev/apis/events-api(Events API リファレンス)