スラッシュコマンドは、入力欄に「/」から始まる文字列を打ち込んで、アプリや機能を呼び出す仕組みです。Slackの/remind、Discordのボットコマンド、Claude Codeの/deployのように、同じ「/」でも中で起きていることは製品ごとに違います。この記事では、まずその違いを整理したうえで、Slackでカスタムスラッシュコマンドを自作する手順を、Bolt for Pythonの最小コード、3秒以内の応答、署名検証の順に解説します。
まとめ:Slackのカスタムスラッシュコマンドで押さえる5点
- Slackのカスタムスラッシュコマンドは、Slackアプリの設定画面で「Command」と「Request URL」を登録し、届いたHTTP POSTに応答する仕組みです。
- 受信から3,000ミリ秒以内にHTTP 200を返さないと、利用者には
operation_timeoutが表示されます。重い処理は先に受領だけ返し、response_urlで後から結果を送ります(30分以内に最大5回)。 - 応答は既定で本人にだけ見える
ephemeralです。チャンネル全員に見せるならresponse_typeにin_channelを指定します。 - リクエストが本当にSlackから来たかは、
X-Slack-SignatureをSigning SecretとHMAC-SHA256で照合して確かめます。旧来のtokenによる検証は非推奨です。 - 開発者が作ったコマンドはスレッド内で呼び出せず、名前空間もありません。同名のコマンドがあると、最後にインストールしたアプリが呼ばれます。
スラッシュコマンドの定義と製品ごとの3つの系統
「スラッシュコマンド」は特定製品の機能名ではなく、「/」を合図に入力欄から機能を呼び出す操作の総称です。呼び出した後に何が動くかで、大きく3つの系統に分かれます。
| 系統 | 代表例 | 「/」の後で動くもの | 自作の方法 |
|---|---|---|---|
| チャットのアプリ呼び出し | Slack、Discord、Mattermost | HTTPまたはWebSocket経由のアプリ呼び出し | アプリを登録し受信・応答処理を書く |
| エディタのコマンド検索 | Obsidian | アプリ内の既存コマンド | 原則不可(プラグインで追加) |
| AIコーディングツール | Claude Code | 定型プロンプトの展開 | Markdownファイルを置く |
たとえばObsidianのスラッシュコマンドは、エディタで「/」を打つとコマンド一覧をあいまい検索して実行できるコアプラグインで、外部へ要求は飛びません。一方Slackのカスタムコマンドは、入力した瞬間に自分のサーバーへデータが送られます。検索結果で「スラッシュコマンド」の解説がツールごとにばらばらに見えるのは、この系統の違いのためです。
以降はチャットの系統、なかでもSlackでの自作を中心に扱います。Claude Codeでのコマンド作成はClaude Codeカスタムコマンドの作り方|Skills統合後の書き方と引数【2026年最新】で解説しています。
Slackの標準スラッシュコマンドとカスタムコマンドの違い
Slackには最初から使える標準コマンドがあり、アプリを作らなくても次のような操作ができます。
| コマンド | できること |
|---|---|
/remind |
自分やチャンネルへのリマインダー設定 |
/msg |
メンバーやチャンネルへのメッセージ送信 |
/invite |
チャンネルへのメンバー招待 |
/status |
ステータスの設定 |
/dnd |
おやすみモードの切り替え |
/topic |
チャンネルのトピック設定 |
カスタムコマンドとの違いは、見た目より制約に出ます。Slackの開発者向けドキュメントは、スレッド内で呼び出せるのは標準コマンドとGiphyのコマンドだけで、開発者が作ったコマンドはスレッド内で使えないと明記しています。同じ理由で、操作がすべてスレッドで進むsplit viewの中でも使えません。
もう一つは名前の扱いです。スラッシュコマンドには名前空間がなく、同じ名前のコマンドを持つアプリが複数入っていると、最後にインストールしたアプリが呼ばれます。/deployや/todoのような一般的な名前は、社内の別チームのアプリと衝突しやすいので、/acme-deployのように組織名や製品名を前に付けておくのが安全です。
Slackでカスタムスラッシュコマンドを自作する手順
作業は「Slackアプリを作る」「コマンドを定義する」「応答するサーバーを書く」の3段です。アプリに付与するトークンやスコープ、Events APIとの関係から押さえたい場合はSlack APIとは?Web API・Events API・Boltの違いと実装手順を参照してください。無料プランのワークスペースでも作れますが、追加できるサードパーティ製アプリとカスタムアプリは合わせて最大10個までです。
アプリ作成とコマンド定義の5項目
Slackのアプリ管理画面でアプリを作成し、左メニューの「Slash Commands」から「Create New Command」を押すと、次の5項目を入力する画面になります。
| 項目 | 入れる内容 | 注意点 |
|---|---|---|
| Command | 利用者が打つ名前 | 名前空間なし=衝突しにくい名前に |
| Request URL | データの送り先URL | 公開配布時はHTTPS必須・自己署名証明書不可 |
| Short Description | コマンドの短い説明 | 候補一覧に表示される |
| Usage Hint | 引数の書き方の例 | 長いと候補表示で切れる |
| Escape channels, users, and links | メンションとURLの変換 | オンでIDに変換されて届く |
Escapeの設定はオンにしておくのが公式の推奨です。オフのままだと@yamadaのような表示名が文字列のまま届き、ユーザーIDへの変換を自前でusers.listなどを使って行う必要があります。オンにすると<@U012AB3CD>の形でIDが届き、公開チャンネルはIDと名前、プライベートチャンネルはIDだけが入ります。
定義を保存したら、アプリをワークスペースにインストールします。スラッシュコマンドを持つアプリにはcommandsスコープが付きます。コマンドではなくメンションやDMで応答させたい場合は、Slack Botの作り方:Bolt for Pythonでメンション返信からLambda運用までを参照してください。
Request URLとSocket Modeの選び方
Slackからの要求を受ける方法は2つあります。インターネットから届くHTTPSのURLを用意する方法と、アプリ側からSlackへWebSocketでつなぎに行くSocket Modeです。
| 比較軸 | Request URL | Socket Mode |
|---|---|---|
| 公開URL | 必要 | 不要 |
| 必要なトークン | Bot token+Signing Secret | Bot token+App token(xapp-) |
| Slack Marketplace掲載 | 可 | 不可 |
| 向く場面 | 本番運用・外部配布 | 社内限定・社内ネットワーク内 |
社内だけで使い、ファイアウォールの内側で動かしたいならSocket Modeが手軽です。Slack Marketplaceへの掲載を予定しているなら、掲載対象外のSocket Modeは選べません。Request URL方式で手元のPCを試験的に公開するなら、ngrokでローカルサーバーを外部公開する手順|認証設定とTraffic Policyの手順で一時URLを発行できます。
Bolt for Pythonでの最小実装
Slack公式のフレームワークBoltを使うと、署名検証や受領応答をライブラリに任せられます。2026年10月時点の最新版はPyPIのPython版がslack-bolt 1.30.0(2026年7月15日公開、Python 3.7以上)、JavaScript版が@slack/bolt 5.1.0(2026年9月2日公開、Node.js 20以上)です。次は/deploy-statusに環境名を渡し、5秒後に固定の確認文をチャンネルへ返す通信サンプルです。実際のデプロイ情報は取得していません。
import os
import time
from slack_bolt import App
app = App(
token=os.environ["SLACK_BOT_TOKEN"],
signing_secret=os.environ["SLACK_SIGNING_SECRET"],
)
@app.command("/deploy-status")
def handle_deploy_status(ack, respond, command):
ack("確認しています") # 3秒以内に受領を返す
env = command["text"].strip() or "production"
time.sleep(5) # 外部APIの呼び出しなど時間のかかる処理の代わり
respond(text=f"{env} の最新デプロイを確認しました", response_type="in_channel")
if __name__ == "__main__":
app.start(port=int(os.environ.get("PORT", 3000)))
python -m pip install slack-bolt==1.30.0で検証時と同じバージョンを入れ、環境変数に Bot token(xoxb-で始まる)と Signing Secret を設定して起動すると、http://<ホスト>:3000/slack/eventsで待ち受けます。Request URLには、ngrokなどで公開したHTTPSのベースURLに/slack/eventsを付けて登録します。ローカルの待受URLをそのまま登録しても、Slackから到達できません。
このコードを slack-bolt 1.30.0 で起動し、Slackと同じ形式の署名付き要求を送って動きを確かめました(Slack APIへのauth.test呼び出しだけをテスト用に差し替え)。結果は次のとおりです。
ack()の時点で「確認しています」がHTTP 200として0.03秒で返り、5秒かかる処理を待たない- 処理の完了後、
response_url宛てに"response_type": "in_channel"付きの結果が約5.2秒後に届く - Signing Secretが違う要求と、タイムスタンプが301秒前の要求は、どちらもHTTP 401で拒否される
Bolt for Pythonは既定でリスナーを別スレッドで動かし、ack()を呼んだ時点でHTTP応答を返します。AWS Lambdaのように応答後に処理を続けられない環境ではprocess_before_response=Trueを指定し、時間のかかる処理はlazyリスナーに分けます。
Slackが送るデータと応答の返し方
コマンドが実行されると、Slackはapplication/x-www-form-urlencoded形式のHTTP POSTをRequest URLへ送ります。応答の組み立てに使う項目は次のとおりです。
ペイロードの主な項目
| 項目 | 中身 | 使い道 |
|---|---|---|
command |
実行されたコマンド名 | 1つのURLで複数コマンドを振り分け |
text |
コマンド名より後ろの入力 | 引数として解釈 |
response_url |
一時的なWebhook URL | 後から結果を送る |
trigger_id |
短時間だけ有効なID | モーダルを開く |
user_id / channel_id / team_id |
実行者と場所のID | 権限判定・投稿先の決定 |
token |
旧式の検証トークン | 非推奨=使わない |
user_name |
実行者の表示名 | 非推奨=user_idを使う |
textにはコマンド名より後ろの入力が文字列として入ります。Escape設定がオンならメンションやURLは変換されますが、アプリ固有の引数の型や必須チェックは実装側で行います。/deploy-status staging --forceのような入力をどう分解するかはアプリ側で決め、想定外の入力にはエラー文を本人だけに返します。
3秒以内の受領応答とresponse_urlでの後追い
Slackは受信確認を3,000ミリ秒以内に求めます。間に合わないと、利用者の画面にはoperation_timeoutのエラーが表示されます。外部APIを呼ぶ、データベースを集計するといった処理は3秒を超えやすいので、Boltの例のように先に受領だけ返し、結果はresponse_urlで送ります。
| 返し方 | 期限・回数 | 向く処理 |
|---|---|---|
| HTTP 200の本文で即答 | 3秒以内に1回 | 定型文・軽い計算 |
response_urlへPOST |
30分以内に最大5回 | 外部API・集計 |
chat.postMessage |
期限なし(Bot tokenで投稿) | 30分を超える処理 |
応答の見え方はresponse_typeで決まり、既定は実行した本人にだけ見えるephemeralです。in_channelを指定すると応答がチャンネル全員に表示され、初回のHTTP応答で指定した場合は利用者が打ったコマンドも一緒にチャンネルに残ります。コマンドを残したいときは、Boltならack(response_type="in_channel")のように受領応答の側で指定します。公式ドキュメントは、既定どおりephemeralで返す場合でもresponse_typeを明示するよう勧めています。受領だけ返して何も表示しないなら空のHTTP 200でもかまいませんが、利用者からは反応がないように見えるため、短い一言を返す方が親切です。
Bolt以外で受けるときの署名検証の実装
Boltを使わず、既存のWebフレームワークやサーバーレス関数で直接受ける場合は、署名検証を自分で書きます。Slackは要求ごとにX-Slack-SignatureとX-Slack-Request-Timestampの2つのヘッダーを付けて送ってきます。
- タイムスタンプが現在時刻から5分を超えてずれていたら、リプレイ攻撃の可能性があるため拒否する
v0:、タイムスタンプ、生のリクエスト本文をコロンでつないだ文字列を作る- Signing Secretを鍵にHMAC-SHA256を計算し、16進表記の先頭に
v0=を付ける - ヘッダーの署名と、タイミング攻撃に強い比較関数で照合する
import hashlib
import hmac
import time
def is_valid_slack_request(signing_secret, timestamp, body, signature, now=None):
try:
int(timestamp)
except (TypeError, ValueError):
return False
if not isinstance(signature, str) or not signature.isascii():
return False
now = time.time() if now is None else now
if abs(now - int(timestamp)) > 60 * 5: # 5分を超えてずれた要求は拒否する
return False
raw_body = body.encode("utf-8") if isinstance(body, str) else body
basestring = f"v0:{timestamp}:".encode("utf-8") + raw_body
expected = "v0=" + hmac.new(signing_secret.encode(), basestring, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
この関数に、Slack公式の署名検証ドキュメントが載せている検証例(Signing Secret、タイムスタンプ1531420618、本文、期待される署名)をそのまま渡すとTrueが返り、同じ値で現在時刻を6分後にするとFalseになることを確認しています。本文はURLデコードやパース前の生のバイト列を使う点が要注意です。フレームワークがフォームを先に解析してしまうと、並び順やエンコードが変わって署名が一致しなくなります。
Discordのスラッシュコマンドとの違い
Discordにもアプリケーションコマンドとしてスラッシュコマンドがありますが、Slackと違ってコマンドの定義を管理画面ではなくAPIで登録します。引数の型や選択肢も登録時に宣言でき、Discordのクライアントが入力を補完します。
| 比較軸 | Slack | Discord |
|---|---|---|
| コマンドの登録 | アプリ管理画面で入力 | APIで登録(グローバル/サーバー単位) |
| 引数 | 文字列1つ(text) |
型付きのオプションを宣言 |
| 初回応答の期限 | 3秒 | 3秒 |
| 後追いの期限 | response_urlで30分・5回 |
トークンで15分 |
| 登録数の上限 | 記載なし | グローバル100件 |
| 名前の規則 | 名前空間なし | 1〜32文字・アプリ内で一意 |
Discordで3秒に間に合わない処理は、応答タイプ5(DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE)で先に受領を返し、利用者に読み込み中を表示したまま15分以内に本文を編集します。開発中はサーバー単位のコマンドで試すのが定石です。サーバー単位のコマンドは即時に反映されるため、修正のたびに待たずに済みます。なお、コマンドの作成はサーバーごとに1日200件までという制限があり、起動のたびに全件を作り直す書き方は避けます。
discord.jsやdiscord.pyでの登録スクリプトと応答ハンドラーの書き方はDiscord Botの作り方|アプリ登録から24時間稼働までを手順どおりに、Gateway接続とHTTP応答の構成の違いはDiscord Botの仕組みと作り方|既製Botと自作の判断基準を実装視点で解説で扱っています。
Claude Codeのカスタムスラッシュコマンドとの違い
Claude CodeなどのAIコーディングツールでも「カスタムスラッシュコマンド」という言葉が使われますが、チャットのコマンドとは別物です。Claude Codeでは.claude/commands/deploy.mdのようにMarkdownファイルを置くと/deployが使えるようになり、実行するとファイルの中身がプロンプトとして展開されます。コマンド登録のために専用の受信サーバーやRequest URLを用意する必要はありません。
引数は$ARGUMENTSで全体を、$0や$1で個別に受け取ります。番号は0始まりで、$0が1つ目の引数です。旧版のこの記事では$ARGUMENTSをGitHub CLIの機能として紹介していましたが、これはClaude Codeの記法です。2026年10月時点のClaude Codeの公式ドキュメントでは、カスタムコマンドはスキル(.claude/skills/<名前>/SKILL.md)に統合されたと説明されています。既存の.claude/commands/のファイルもそのまま動き、同じ名前のスキルとコマンドがあればスキルが優先されます。
カスタムスラッシュコマンドが動かないときの切り分け
Slackでよく起きる症状と、最初に確認する箇所をまとめます。
| 症状 | 疑う原因 | 確認する箇所 |
|---|---|---|
operation_timeoutが出る |
受領応答が3秒を超えた | 重い処理の前にack()しているか |
| 候補に出ない・スレッドで使えない | スレッド内では呼べない仕様 | チャンネル本体で実行する |
| 別のアプリが反応する | 同名コマンドの衝突 | 最後に入れたアプリが優先される |
| サーバーが401を返す | Signing Secretの取り違え・時刻ずれ | サーバー時計のNTP同期 |
| 結果が本人にしか見えない | 既定のephemeral |
response_typeの指定 |
| 30分後の結果が届かない | response_urlの期限切れ |
chat.postMessageへ切り替え |
署名検証の失敗は、時計ずれのほかに、ロードバランサーやフレームワークが本文を書き換えている場合にも起きます。まず、フォーム解析前の本文を署名検証へ渡しているか確認します。診断ログには本文全体を残さず、バイト長や処理段階ごとのハッシュ値を記録し、本文の変化を調べます。
スラッシュコマンドを作らない方がよい場面
スラッシュコマンドは「人が打って、アプリが返す」双方向の操作に向いた仕組みです。次の3つに当てはまるなら、別の手段の方が少ない手間で済みます。
- 監視アラートやビルド結果を流すだけ:人の入力を受けないなら、URLにJSONを送るだけのIncoming Webhookで足ります。応答サーバーも署名検証も要りません。実装はSlack Incoming Webhookの実装手順:URL発行・Block Kit整形・SDK送信と429対策を参照してください。
- スレッドの文脈から操作を始めたい:カスタムコマンドはスレッド内で呼べません。特定のメッセージを起点にしたいなら、メッセージのメニューから起動するメッセージショートカットを使うと、元のメッセージの内容がペイロードに入ります。ショートカットはアプリごとにグローバル5件、メッセージ5件まで作れます。
- 無料プランでアプリ枠に余裕がない:無料ワークスペースのアプリ上限は10個です。コマンドごとにアプリを分けると枠を圧迫するため、1つのアプリに複数のコマンドを登録し、
commandの値で振り分けます。
逆に、障害対応中にデプロイ状況や当番表を引くように、利用者が引数を打って結果を受け取る操作にはスラッシュコマンドが向いています。会話画面を離れずに、環境名などの引数を添えてそのまま照会できるためです。迷ったら「人が打つか」「引数があるか」の2点で判断してください。
よくある質問
スラッシュコマンドとは何ですか?
入力欄に「/」から始まる文字列を打って、アプリや機能を呼び出す操作の総称です。Slackでは外部サーバーへの要求、Obsidianではアプリ内コマンドの検索、Claude Codeでは定型プロンプトの展開と、製品によって中身が異なります。
Slackのスラッシュコマンドは無料プランでも自作できますか?
作れます。ただし無料ワークスペースで追加できるサードパーティ製アプリとカスタムアプリは合わせて最大10個です。複数のコマンドは1つのアプリにまとめて登録できます。
自作したスラッシュコマンドがスレッドで使えないのはなぜですか?
Slackの仕様です。スレッド内で呼び出せるのは標準コマンドとGiphyのコマンドだけで、開発者が作ったコマンドはスレッドでは使えません。メッセージを起点にしたい場合はメッセージショートカットを検討してください。
Slackで「operation_timeout」と表示されるのはなぜですか?
アプリがSlackの要求に3,000ミリ秒以内にHTTP 200を返せていません。重い処理の前に受領応答を返し、結果はresponse_urlで後から送る構成にすると解消します。
Discordのスラッシュコマンドの作り方はSlackと同じですか?
応答期限が3秒という点は同じですが、Discordはコマンドを管理画面ではなくAPIで登録し、引数の型も宣言します。開発中は即時反映されるサーバー単位のコマンドで試し、完成後にグローバルへ登録するのが一般的です。