この記事では、「問い合わせ受付」のSlack通知を、読んで動ける形に育てます。Block Kitで見やすくし、種類ごとに担当者へメンションし、続報はスレッドに返し、見積もりには受付メールを送る前にボタンで承認を挟みます。逆向きに、Slackのリアクションで「対応済み」にする流れと、スラッシュコマンドで未対応の一覧を出す流れも作ります。前提は「n8nの使い方入門:最初のワークフローを作って動かす」の3ノード(n8n Form Trigger「問い合わせフォーム」→Google Sheets→Slack)が動いていることです。n8nの版は2026年10月時点の安定版2.44.7で書いています。
先に決めること
ボットのトークンでつなぐか、OAuth2でつなぐか
SlackのCredential(認証情報)は「Access Token」と「OAuth2」の2種類で、Slackの上で誰として動くかが違います。
| つなぎ方 | n8nが使うトークン | 投稿の名前 | Slack Trigger |
|---|---|---|---|
| Access Token(Bot User OAuth Token) | ボットのトークン(xoxb-) | アプリの名前 | 使える(これしか使えない) |
| OAuth2(セルフホストで自分のアプリ) | 許可した人のユーザートークン | 許可した人の名前 | 使えない |
| OAuth2(n8n Cloudの「Connect my account」) | 許可した人のユーザートークン | 許可した人の名前 | 使えない |
公式ドキュメントはSlackノードにOAuth2を勧めていますが、n8nのOAuth2は許可した人のユーザートークンで動きます。担当者が許可すると通知がその人の名前で出て、その人のアカウントが止まった日に通知も止まります。業務の通知はボットのトークン(Access Token)でつなぎます。Slack Triggerもこれでしか動かないので、Credentialが1つにまとまります。OAuth2が要るのは、メッセージの検索のようにユーザートークンでしか呼べない操作だけです。
アプリを何個作るか
Slackのアプリは、イベントの送り先(Request URL)を1つしか持てません。試すときのTest URLと本番のProduction URLを同じアプリで行き来すると、試している間は本番にイベントが届きません。運用しているn8nでは、本番用と試験用でアプリを分けています。この記事は本番用の1つで書きます。
承認ボタンをどちらの方式にするか
「Send and Wait for Response」(送信して返事を待つ)のボタンは、初期設定だとリンクで、押すとブラウザでn8nのページが開き、誰が押したかは残りません。「Capture Who Responded」(押した人を記録)をオンにすると、Slackの中で1回押せば決まり、押した人が残り、押せる人も絞れます。その代わり、n8nがインターネットから届くHTTPSで動いていることと、SlackアプリにInteractivity(クリックの送り先)の設定が要ります。社外への送信に関わる承認は、押した人が残る方式にします。
新着は #問い合わせ、承認は同じ投稿のスレッド、n8nのエラーは #n8n-監視 に分けます。エラー通知の作り方は「n8nを止めずに運用する:アップデート・バックアップ・エラー通知・脆弱性」のとおりで、同じアプリを使えます。
作るものとスコープの一覧
| ワークフロー | きっかけ | Slackでやること | Slackアプリ側の設定 |
|---|---|---|---|
| 問い合わせ受付(育てる) | フォームの送信 | Block Kitで通知、メンション、スレッドに続報、ボタンで承認 | Bot Token Scopes、Interactivity |
| 対応済みにする | 通知に ✅ のリアクション | シートを「対応済み」にし、スレッドに返す | Event Subscriptions |
| 未対応を答える | /toiawase と打つ | 未対応の一覧を、打った人にだけ返す | Slash Commands |
アプリに付けるボットのスコープ(Bot Token Scopes)です。足りないとn8nは「Your Slack credential is missing required Oauth Scopes」と出し、足りないスコープ名を添えます。
| スコープ | 何に使うか |
|---|---|
chat:write | 投稿・スレッドへの返信・承認の依頼・投稿の書き換え |
channels:read / groups:read | 公開/非公開チャンネルを一覧から選ぶ |
users:read / users:read.email | ユーザーの一覧、承認できる人の指定、押した人の名前とメール |
usergroups:read | ユーザーグループ(@営業 など)のIDを調べる |
reactions:read | リアクションのイベントを受ける |
commands | スラッシュコマンドを作る |
channels:history | 新しい投稿のイベントを受ける(使うときだけ) |
app_mentions:read | アプリへのメンションを受ける(使うときだけ) |
アプリが入っていない公開チャンネルに送れる chat:write.public は付けません。送り先のチャンネルにだけアプリを追加する運用のほうが、どこに流れているかを後から追えます。
手順
1. Slackアプリを作る
画面で1つずつ足すより、マニフェスト(アプリの設定をまとめたJSON)で作るほうが漏れがありません。公式ドキュメントもこちらを勧めています。
- api.slack.com/apps を開き、「Create New App」から「From a manifest」を選ぶ
- ワークスペースを選んで「Next」
- 「JSON」タブの中身を消して下のマニフェストを貼り、
n8n.example.co.jpを自社のドメインに直す - 「Next」で内容を確かめ、「Create」
- 「OAuth & Permissions」の「Install to Workspace」を押し、「Allow」(ワークスペースの管理者が押す)
- 同じ画面の「Bot User OAuth Token」(
xoxb-で始まる)を控える - 「Basic Information」の「App Credentials」にある「Signing Secret」を控える
- Slackで #問い合わせ チャンネルの詳細を開き、作ったアプリ(問い合わせ通知)をチャンネルに追加してメンバーにする
{
"display_information": { "name": "問い合わせ通知" },
"features": {
"bot_user": { "display_name": "toiawase", "always_online": true },
"slash_commands": [
{
"command": "/toiawase",
"url": "https://n8n.example.co.jp/webhook/slack-toiawase",
"description": "未対応の問い合わせを表示します",
"should_escape": true
}
]
},
"oauth_config": {
"scopes": {
"bot": ["chat:write", "channels:read", "groups:read", "users:read", "users:read.email", "usergroups:read", "reactions:read", "commands"]
}
},
"settings": {
"interactivity": {
"is_enabled": true,
"request_url": "https://n8n.example.co.jp/webhook-waiting-slack"
},
"token_rotation_enabled": false,
"socket_mode_enabled": false,
"org_deploy_enabled": false
}
}
アプリ名は日本語にできますが、ボットの表示名(display_name)は英小文字・数字・-・_・. だけです。token_rotation_enabled は必ず false にします。トークンの自動更新を有効にすると12時間でトークンが切れ、一度有効にすると戻せないので、アプリを作り直すことになります。イベントの送り先はここでは入れず、手順6でワークフローを公開してから足します。Slackは登録の瞬間にそのURLへ確認を送るので、n8n側が待ち受けていないと登録できないからです。
2. n8nにCredentialを作る
- 「問い合わせ受付」のSlackノードを開き、「Authentication」が「Access Token」(初期値)であることを確かめる
- Credentialの欄で「Create new credential」を選ぶ
- 「Access Token」に
xoxb-のトークン、「Signature Secret」にSigning Secretを貼って保存する - 名前を「Slack 問い合わせ通知(bot)」のように、どのアプリのどのトークンか分かる名前にする
Signature Secretは空でも投稿はできますが、Slack Triggerが要求の送り主を確かめるのにも、押した人を記録する承認にも使います。無いと承認のボタンを押してもワークフローが進みません。n8n Cloudでも、Slack Triggerを使うならこの形で作ります。Credentialの共有や権限は「n8nの認証情報(Credential):OAuth2・APIキー・共有と権限」に書きました。

3. 通知をBlock Kitで組む
社外の人が書いた文をそのままSlackに流すと、本文に <!channel> とあっただけでチャンネル全員への通知になります。Slackの書式では &・<・> の3文字を置き換えれば済むので、Slackノードの前に整えるノードを挟みます。
- 「問い合わせ一覧に追加」とSlackの間の + から「Edit Fields (Set)」を足し、名前を「Slack用に整える」にする
- 「Mode」は「Manual Mapping」のまま、「Add Field」で次の項目を作る(型はすべてString)
| 名前 | 値 |
|---|---|
| company | {{ $('問い合わせフォーム').item.json.company.replace(/[&<>]/g, c => ({ '&': '&', '<': '<', '>': '>' })[c]) }} |
| name・message | companyと同じ式で company を name・message に変える。message は末尾に .slice(0, 2900) を足す |
| category | {{ $('問い合わせフォーム').item.json.category }} |
| mention | {{ ({ 'お見積もり': '<!subteam^S0123ABCD>', 'サービスについて': '<@U0123ABCD>' })[$('問い合わせフォーム').item.json.category] ?? '' }} |
本文を2,900字で切るのは、Block Kitの文の区画(section)が3,000字までで、超えると投稿ごと受け付けられないからです。
メンションは @山田 と書いても通知されません。人は <@メンバーID>、ユーザーグループは <!subteam^グループID> と書きます。IDはSlackノードで調べられます。「Resource」を「User」、「Operation」を「Get Many」にして一度実行すると、OUTPUTの id に各メンバーのIDが並びます。グループは「Resource」を「User Group」にすれば同じように出ます。Slackの説明では、人へのメンションはその人がチャンネルのメンバーのときだけ通知されるので、担当者は #問い合わせ に入れておきます。
次にSlackノードを直します。
- 「Message Type」を「Blocks」にする
- 「Blocks」の欄を「Expression」に切り替え、下のJSONを貼る
- 「Notification Text」に
新しい問い合わせ: {{ $json.company }}({{ $json.category }})と入れる - ノード名を「新着を知らせる」に変える
{
"blocks": [
{ "type": "header", "text": { "type": "plain_text", "text": "新しい問い合わせ({{ $json.category }})" } },
{
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": {{ JSON.stringify('*会社名*\n' + $json.company) }} },
{ "type": "mrkdwn", "text": {{ JSON.stringify('*お名前*\n' + $json.name) }} },
{ "type": "mrkdwn", "text": {{ JSON.stringify('*担当*\n' + ($json.mention || '未定')) }} }
]
},
{ "type": "section", "text": { "type": "mrkdwn", "text": {{ JSON.stringify($json.message) }} } },
{ "type": "context", "elements": [ { "type": "mrkdwn", "text": "受付番号 {{ $execution.id }} 対応したら :white_check_mark: を付けてください" } ] }
]
}
欄を「Expression」にしないと {{ }} がそのままの文字で送られます。値を "{{ $json.message }}" のように引用符で囲むと、本文に " や改行があった瞬間にJSONが壊れるので、JSON.stringify() で包みます。n8nが受け付けるのは { "blocks": [ ... ] } の形で、[ ... ] だけではエラーです。見た目はSlackのBlock Kit Builderで作り、値の部分だけ式に差し替えると早いです。
「Notification Text」はスマートフォンの通知に出る文です。Slackの説明では、スマートフォンの通知はBlocksの中身を使わずこの文だけを出すので、空だと何の問い合わせか分かりません。

4. 投稿IDを残して、続報をスレッドに返す
Slackの投稿には ts という番号が付き、これを指定するとその投稿のスレッドに返信できます。n8nのSlackノードは、送った投稿の ts を message_timestamp という名前でOUTPUTに出すので、シートに残せば別のワークフローからも同じスレッドに返せます。
- 「問い合わせ一覧」のH列に「受付番号」、I列に「Slack投稿ID」の見出しを足す
- 「問い合わせ一覧に追加」で「Mapping Column Mode」を選び直して列を読み込み直し、「受付番号」に
{{ $execution.id }}を入れる - 「新着を知らせる」の後ろにGoogle Sheetsの「Update Row」を足し、名前を「投稿IDを記録」にする
- 「Column to match on」を「受付番号」、値を
{{ $execution.id }}、「Slack投稿ID」を{{ $json.message_timestamp }}にする - 「Add option」から「Cell Format」を足し、「Let n8n format」を選ぶ
$execution.id は実行ごとに変わる番号です。行を探すキーに受付日時を使うと、同じ時刻に2件来たときに別の行を書き換えます。「Cell Format」を変えたのは、初期値の「Let Google Sheets format」だと 1729912345.123456 のような投稿IDが数値になり、桁が丸められて後で照合できなくなるからです。シートの扱いは「n8nとGoogleスプレッドシート連携:読み書き・更新・重複を防ぐ」に分けました。
スレッドに返すときは、Slackの「Send」で「Add option」から「Reply to a Message」を足し、「Message Timestamp to Reply To」に投稿IDを入れます。「Also Send to Channel」をオンにすると返信がチャンネルにも出ますが、続報のたびにオンにするとスレッドに分けた意味がなくなるので、全員が知るべき結果だけにします。
5. 送信の前に承認を挟む
見積もりの問い合わせには受付メールを自動で返します。ただ、競合の偵察や取引できない相手も混ざるので、送る前に営業の責任者が確かめます。
- 「投稿IDを記録」の後ろに「If」を足し、
{{ $('問い合わせフォーム').item.json.category }}が「お見積もり」と等しい、にする - true の出口にSlackを足し、操作を「Send and Wait for Response」、名前を「送信の承認」にする
- 「Send Message To」を「Channel」(初期値は「User」)にし、#問い合わせ を選ぶ
- 「Message」に下の文を入れる
- 「Response Type」は「Approval」のまま、「Approval Options」で「Type of Approval」を「Approve and Disapprove」、ボタンの文字を「送る」「送らない」にする
- 「Capture Who Responded」をオンにし、「Restrict Who Can Approve」で営業の責任者2名を選ぶ
- 「Options」で「Reply to a Message」を足し、
{{ $('新着を知らせる').item.json.message_timestamp }}を入れる - 「Options」で「Limit Wait Time」を足して1日にし、「Append n8n Attribution」を足してオフにする
- 後ろに「If」を足し、
{{ $json.data.approved }}が true のときにする - true の出口にGmailで受付メールを送るノード、false の出口に「送らずに止めました」とスレッドに返すSlackノードをつなぐ
<!subteam^S0123ABCD> 受付メールを送ってよいか確認してください
宛先: {{ $('問い合わせフォーム').item.json.email }}
会社名: {{ $('Slack用に整える').item.json.company }}
出力には approved(承認したか)と respondedAt(押した日時)に加え、responder に押した人のID・名前・メールアドレスが入ります。リスト外の人が押すと、その人にだけ断りの文が見えてワークフローは待ち続けます。文は「Unauthorized Reply」で日本語に変えられます。押されたあとは、初期値の「Show Outcome and Remove Buttons」でボタンが消え、誰が決めたかの1行が付くので、二重に押されません。
「Limit Wait Time」を入れないと、誰も押さない限り実行は「Waiting」のまま待ち続けます。2.44.7のSlackノードのソースを読むと、待ちに入るときこのノードは受け取ったデータをそのまま出力として持っておき、ボタンが押されたときだけ data.approved 付きの結果に置き換えます。そのため1日で切れたときは data.approved が付かず、「承認されたか」の false 側に流れてメールは送られない作りです。時間切れの動きは、本番の前に「Amount」を数分にして一度確かめておきます。Gmailでの送り方は「n8nとGmail連携:受信をきっかけに動かす、送る、振り分ける」に書きました。

AIのエージェントが道具を使う前に挟む「Human review」も承認先にSlackを選べます。組み方は「n8nでAIエージェントを作る:ChatGPT・Claude・Geminiをつなぐ」にあります。
6. リアクションで「対応済み」にする(Slack Trigger)
対応した人が通知に ✅ を付けたら、シートの「対応状況」を「対応済み」にします。シートを開かずに済むので、実際に本番で動かしていると更新漏れがかなり減ります。
- 新しいワークフロー「対応済みにする」を作り、最初に「Slack Trigger」を置いて手順2のCredentialを選ぶ
- 「Trigger On」を「Reaction Added」、「Channel to Watch」を #問い合わせ にする
- 「Options」から「Emoji Names to Filter」を足し、
white_check_markと入れる(コロンは付けない) - 後ろにGoogle Sheetsの「Update Row」を足し、「Column to match on」を「Slack投稿ID」、値を
{{ $json.item.ts }}、「対応状況」を「対応済み」にする - 後ろにSlackの「Send」を足し、「Reply to a Message」に
{{ $('Slack Trigger').item.json.item.ts }}、本文に<@{{ $('Slack Trigger').item.json.user }}> さんが対応済みにしましたと入れる - 「Publish」を押し、Slack Triggerの「Webhook URLs」から「Production URL」をコピーする
- api.slack.com/apps でアプリの「Event Subscriptions」を開き、「Enable Events」をオンにして「Request URL」に貼る。「Verified」と出れば登録済み
- 「Subscribe to bot events」に
reaction_addedを足して「Save Changes」を押し、再インストールを求められたら「Allow」まで進む
絵文字とチャンネルに合わないイベントは、n8nが受け取った時点で捨て、実行も作りません。「Watch Whole Workspace」をオンにすると全チャンネルのイベント1つごとに1実行を使うので、n8n Cloudでは必ず絞ります。✅ は親の通知に付けてもらいます。返信に付けると別の ts が届き、シートの行と一致しません。
7. スラッシュコマンドで未対応を答える
/toiawase と打つと、未対応の一覧が打った人にだけ出るようにします。スラッシュコマンドはSlack Triggerでは受けられないので、「Webhook」ノードで受けます。
Slackはスラッシュコマンドに3秒(3,000ミリ秒)以内の返事を求め、遅れると打った人に operation_timeout が出ます。シートを読んでいると3秒を超えることがあるので、先に「集計しています」と返し、一覧は要求に付いてくる一時的なURL(response_url)へ後から送ります。
- 新しいワークフロー「未対応を答える」を作り、「Webhook」を置いて名前を「Slackコマンド」にする
- 「HTTP Method」を「POST」、「Path」を
slack-toiawase、「Respond」を「Using ‘Respond to Webhook’ Node」にする - すぐ後ろに「Respond to Webhook」を足し、「Respond With」を「JSON」、「Response Body」を
{"response_type": "ephemeral", "text": "集計しています"}にする - 後ろにGoogle Sheetsの「Get Row(s)」を足し、「Filters」で「対応状況」が「未対応」の行にする。「Settings」で「Always Output Data」をオンにする
- 後ろに「Code」を足し、「Mode」を「Run Once for All Items」にして下のコードを入れる
- 後ろに「HTTP Request」を足し、「Method」を「POST」、「URL」を
{{ $('Slackコマンド').item.json.body.response_url }}にする - 「Send Body」をオン、「Body Content Type」を「JSON」、「Specify Body」を「Using JSON」にし、「JSON」に
{{ JSON.stringify({ response_type: 'ephemeral', text: $json.text }) }}と入れて「Publish」
const rows = $input.all().filter(i => i.json['会社名']);
const text = rows.length === 0
? '未対応の問い合わせはありません'
: `未対応は${rows.length}件です\n` +
rows.map(i => `・${i.json['受付日時']} ${i.json['会社名']}(${i.json['種類']})`).join('\n');
return [{ json: { text } }];
「Always Output Data」をオンにしないと、0件のときに後ろが動かず「集計しています」で終わります。オンにすると空の item が1件流れるので、コードでは会社名がある行だけを数えています。Codeノードは「n8nのCodeノード:JavaScriptとPythonで足りないところを書く」で詳しく書きます。ephemeral は打った人にだけ見える返事で、in_channel にするとチャンネル全員に見えます。Slackからはフォームの形式で届き、$json.body.text(コマンドの後ろの文字)や $json.body.user_id(打った人)で取れます。

Webhookノードには、Slack Triggerのような署名の確認がありません。最低限、「Options」の「Only Run If」に {{ $json.body.team_id === 'T0123ABCD' && $json.body.api_app_id === 'A0123ABCD' }} を入れ、自社のワークスペースとアプリ以外からの要求では実行を作らないようにします。この2つは秘密ではないので、書き込みをするコマンドならSlackの署名(v0:タイムスタンプ:本文 をSigning SecretでHMAC-SHA256したもの)を確かめます。考え方は「n8nで外部サービスとつなぐ:Webhook・HTTP Request・API」と同じです。
応用
/toiawase 見積と打ったらその種類だけに絞る。Codeノードで$('Slackコマンド').first().json.body.textを読んで絞り込みに足す- 手順7のCodeノードを「Schedule Trigger」につなぎ、毎朝9時に未対応を1通にまとめて #問い合わせ に送る
- 「送らない」が押されたら、Slackの「Update」でもとの通知の見出しに「見送り」と付ける
できないことと、その代わり
| できないこと | 代わりの手 |
|---|---|
| Slack Triggerでスラッシュコマンドやボタンのクリックを受ける | 「Webhook」ノードで受ける。Slack Triggerが受けるのは投稿・リアクション・メンションなどのイベントだけ |
| Slack TriggerをOAuth2で動かす | ボットのトークン(Access Token)のCredentialを作る |
| 1つのアプリで試験用と本番用のイベントの送り先を持つ | 試験用のアプリを別に作る |
| 押した人を記録する承認と、自作のボタンを同じアプリで受ける | Interactivityの送り先は1つなので、アプリを分ける |
| スレッドの中でスラッシュコマンドを使う | アプリへのメンション(app_mention)をSlack Triggerで拾う |
| Slackノードにない操作(モーダルを開くなど) | 「HTTP Request」の「Authentication」でSlackのCredentialを選び、SlackのAPIを直接呼ぶ |
よく詰まるところ
| 症状 | 原因 | 直し方 |
|---|---|---|
| 「Your Slack credential is missing required Oauth Scopes」 | スコープが足りない | エラーに出たスコープを「OAuth & Permissions」に足し、アプリを再インストールする |
not_in_channel で投稿できない | アプリがチャンネルのメンバーでない | チャンネルの詳細からアプリを追加する |
ratelimited で止まる | 同じチャンネルへの投稿がおよそ1秒1件を超えた | 1通にまとめる。「Settings」の「Retry On Fail」をオンにし「Wait Between Tries (ms)」を長めにする |
スラッシュコマンドで operation_timeout | 3秒以内に返事をしていない | Webhookの直後に「Respond to Webhook」を置いて先に返す |
| 「Request URL」がVerifiedにならない | 公開していない、URLが localhost、Signature Secretが違う | Publishしてから貼る。外向きのURLとSigning Secretを確かめる |
| 同じリアクションで2回、3回動く | n8nが3秒以内に答えられず、Slackがやり直しを送った | 負荷を見直し、重複が困る処理の前に「Remove Duplicates」の「Remove Items Processed in Previous Executions」を置く |
| 「New Message Posted to Channel」で、ワークフローが投稿するたびにまた動く | 動いた結果の投稿を、同じトリガーがもう一度拾っている | 「Options」の「Usernames or IDs to Ignore」に、その投稿をしているユーザー(アプリのボット)を入れる |
| 12時間ほどでつながらなくなる | トークンの自動更新が有効 | 自動更新を無効にしたアプリを作り直し、トークンを入れ直す |
Blocksでエラー、{{ }} がそのまま届く | Expressionになっていない、値の " や改行でJSONが壊れた | Expressionに切り替え、値は JSON.stringify() で包む |
| 承認のボタンを押しても進まない | Signature Secretが無いか違う、Interactivityの送り先が違う | Signing Secretを入れ直し、送り先を /webhook-waiting-slack にする |
リンクのボタンを押すと localhost が開く | n8nが外向きのURLを知らない | 環境変数 N8N_WEBHOOK_URL に https://n8n.example.co.jp/ を入れて再起動する |
次にやること
- n8nで外部サービスとつなぐ:Webhook・HTTP Request・API
- n8nの認証情報(Credential):OAuth2・APIキー・共有と権限
- n8nを止めずに運用する:アップデート・バックアップ・エラー通知・脆弱性
- n8nの使い方入門:最初のワークフローを作って動かす