2026年5月28日

2026年5月28日

PythonでSlack通知を送る方法(Incoming WebhookとWeb APIの使い分け)

Python から Slack に通知を送るいちばん簡単な方法は、Slack アプリで Incoming Webhook の URL を発行し、その URL に JSON を POST することです。複数のチャンネルに送りたい、スレッドに返信したい、といった用途ではボットトークンを使った Web API(chat.postMessage)を選びます。この記事では両方の送り方と、URL やトークンの安全な管理、レート制限やエラーへの備えまでを、サーバーの監視やバックアップ通知を例に説明します。

2つの送り方と選び方

Incoming Webhook Web API(chat.postMessage)
必要なもの Webhook URL ボットトークン(xoxb- で始まる)
送信先 発行時に選んだ1チャンネル ボットが参加している任意のチャンネル
スレッド返信・投稿の更新 できない できる
向いている用途 監視・バックアップ・デプロイの定型通知 送り先を切り替える通知、対話的なボット

「決まったチャンネルに知らせるだけ」なら Incoming Webhook で十分です。迷ったら Webhook から始め、足りなくなったら Web API に移るのが手間の少ない進め方です。

Slack 側の準備(Incoming Webhook)

Webhook は Slack アプリの機能として発行します。大まかな流れは次のとおりです(Slack の管理画面の表記は変わることがあるため、2026年10月現在の一般的な手順として読んでください)。

  1. Slack API の管理画面(api.slack.com/apps)で新しいアプリを作成し、対象のワークスペースを選ぶ
  2. アプリの設定で Incoming Webhooks を有効にする
  3. ワークスペースへの追加を行い、投稿先のチャンネルを選んで許可する
  4. 発行された Webhook URL(hooks.slack.com で始まる)を控える

ワークスペースの設定によっては、アプリの追加に管理者の承認が必要です。古い「カスタムインテグレーション」形式の Webhook は Slack が非推奨としているため、新しく作るならアプリ形式を使ってください。

Webhook URL は秘密情報として扱う

Webhook URL を知っていれば、誰でもそのチャンネルに投稿できます。コードに直書きしたり、Git リポジトリに含めたりせず、環境変数や権限を絞った設定ファイルから読み込みます。

# 例: 実行ユーザーだけが読める環境ファイルに置く
install -m 600 /dev/null ~/.slack.env
echo 'SLACK_WEBHOOK_URL=your-webhook-url' > ~/.slack.env

万一 URL が漏れたら、アプリの設定画面からその Webhook を削除して新しく発行し直します。ログにリクエスト内容を出力する処理を書くときも、URL がそのまま記録されないように気をつけてください。

標準ライブラリだけで送る

追加ライブラリを入れられないサーバーでも、urllib で送れます。

import json
import os
import urllib.error
import urllib.request


def post_to_slack(text: str) -> None:
    url = os.environ["SLACK_WEBHOOK_URL"]
    data = json.dumps({"text": text}).encode("utf-8")
    req = urllib.request.Request(
        url, data=data, headers={"Content-Type": "application/json"}, method="POST"
    )
    try:
        with urllib.request.urlopen(req, timeout=10) as res:
            res.read()
    except urllib.error.HTTPError as e:
        detail = e.read().decode("utf-8", errors="replace")
        raise RuntimeError(f"Slackへの送信に失敗しました: {e.code} {detail}") from e


if __name__ == "__main__":
    post_to_slack("テスト通知です")

成功すると HTTP 200 が返ります。失敗時はレスポンス本文に理由が短い文字列で入るので、上のように本文まで読んでエラーメッセージに含めると原因を追いやすくなります。timeout を付けておかないと、ネットワーク障害のときにスクリプトが止まったままになることがあります。

requests でレート制限に備えて送る

requests を使えば、もう少し短く書けます。Slack は短時間に大量の投稿があると HTTP 429 を返し、Retry-After ヘッダーで待つべき秒数を知らせます。目安として「1チャンネルあたり1秒に1件程度」とされていますが、正確な上限は Slack の公式ドキュメントで確認してください。

import os
import time

import requests


def post_to_slack(payload: dict, retries: int = 3) -> None:
    url = os.environ["SLACK_WEBHOOK_URL"]
    for _ in range(retries):
        r = requests.post(url, json=payload, timeout=10)
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "1")))
            continue
        r.raise_for_status()
        return
    raise RuntimeError("レート制限が続いたため送信を中止しました")

ループの中で1件ずつ通知を送るより、結果をまとめて1通にするほうが、受け取る側にも読みやすく、レート制限にもかかりにくくなります。

Block Kit で見やすいメッセージにする

blocks を使うと、見出しや項目を整理したメッセージを作れます。blocks を使う場合も text を一緒に入れてください。スマートフォンのプッシュ通知などでは text の内容が表示されます。

def escape(s: str) -> str:
    """mrkdwn で特別な意味を持つ記号を置き換える"""
    return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")


site = "example.com"
error = "<tar> exited with code 2"

payload = {
    "text": f"バックアップに失敗しました: {site}",
    "blocks": [
        {"type": "header", "text": {"type": "plain_text", "text": "バックアップ失敗"}},
        {
            "type": "section",
            "fields": [
                {"type": "mrkdwn", "text": f"*サイト*\n{escape(site)}"},
                {"type": "mrkdwn", "text": f"*内容*\n{escape(error)}"},
            ],
        },
    ],
}
post_to_slack(payload)

Slack のメッセージ書式(mrkdwn)では &、<、> が特別な意味を持つため、エラーメッセージやファイル名など外から来た文字列は置き換えてから埋め込みます。そのままだと表示が崩れたり、意図しないメンションやリンクになったりします。レイアウトは Slack が提供する Block Kit Builder で試しながら組み立てると早いです。

Web API(chat.postMessage)で送る

複数チャンネルへの送信やスレッド返信が必要なら、公式の Python SDK(slack_sdk)とボットトークンを使います。

pip install slack_sdk

アプリの設定でボットトークンのスコープに chat:write を追加し、ワークスペースに再インストールするとボットトークンが発行されます。投稿先のチャンネルにはボットを招待しておきます(チャンネルで /invite @アプリ名)。

import os

from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
from slack_sdk.http_retry.builtin_handlers import RateLimitErrorRetryHandler

client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
client.retry_handlers.append(RateLimitErrorRetryHandler(max_retry_count=2))

CHANNEL = "C0123456789"  # チャンネルID(チャンネル名より変更に強い)

try:
    res = client.chat_postMessage(channel=CHANNEL, text="デプロイを開始しました")
    client.chat_postMessage(channel=CHANNEL, thread_ts=res["ts"], text="デプロイが完了しました")
except SlackApiError as e:
    print("Slack API エラー:", e.response["error"])

最初の投稿の ts を thread_ts に渡すと、その投稿へのスレッド返信になります。開始と完了をひとつのスレッドにまとめれば、チャンネルが通知で埋まりにくくなります。RateLimitErrorRetryHandler を追加しておくと、429 が返ったときに SDK が待ってから再送します。

うまく送れないときの確認順

エラーの内容から原因を絞り込めます。よくあるものを、起きやすい順に並べます。

  1. Webhook で 403 や 404 が返る:URL の貼り間違いや、Webhook・アプリが削除されている場合です。環境変数に余計な空白や改行が入っていないかも確認します。
  2. **not_in_channel や channel_not_found**(Web API):ボットがチャンネルに参加していないか、チャンネルIDが違います。非公開チャンネルはボットの招待が必要です。
  3. **missing_scope**:トークンに chat:write がありません。スコープを追加したら、アプリの再インストールが必要です。
  4. **invalid_auth や not_authed**:トークンの値が誤っているか、無効になっています。
  5. cron から実行したときだけ失敗する:cron 環境では環境変数が読み込まれていないことが多いです。crontab で定義するか、ラッパーで環境ファイルを読み込みます。

エラーコードの一覧と意味は、Slack の API ドキュメントの各メソッドのページに載っています。表記や仕様は更新されることがあるので、困ったときは公式の情報を確認してください。

運用でのポイント

  • 通知は「対応が必要なもの」に絞る。成功の通知まで送ると、肝心の失敗が埋もれます。
  • 通知の失敗で本来の処理(バックアップなど)を止めない。送信部分は例外を捕まえてログに残し、処理は続けます。
  • 個人情報や秘密情報をメッセージに含めない。Slack のメッセージはワークスペースのメンバーが検索できます。
  • テスト用のチャンネルで確認してから、本番のチャンネルに切り替える。

まとめ

  • 決まったチャンネルへの定型通知なら Incoming Webhook、送り先の切り替えやスレッド返信が必要なら chat.postMessage を使う
  • Webhook URL とボットトークンは秘密情報。環境変数や権限を絞ったファイルで管理し、漏れたら発行し直す
  • timeout の指定と 429(Retry-After)への対応を入れ、通知はまとめて送る
  • Block Kit を使うときも text を入れ、外部の文字列は &・<・> を置き換えてから埋め込む
  • 送れないときはエラーコードから、URL・チャンネル参加・スコープ・トークン・cron の環境変数の順に確認する

お気軽にご相談ください

お見積りへ お問い合わせへ
▲