n8nでSlack返信を使った承認フローを実装した方法
n8nでAI記事生成フローを作るとき、「AIだけで記事を書いてもどこかで見た内容になる」という課題がありました。差別化するには、実際に経験した失敗・判断・数値の変化など、その人にしか書けない一次情報が必要です。そこで考えたのが、Slackにインタビュー質問を送り、スレッドへの返信を受け取ってから記事生成へ進む承認フローでした。
この記事でわかること:
- n8n Slack Events APIとWebhookをつなぐ方法
- url_verificationチャレンジの処理方法
- スレッド返信だけを処理対象にする絞り込み設計
- 誤送信・テスト返信でフローが動いてしまう問題への対策
- LLM品質判定で回答の質をチェックする方法
- Slackリトライによる二重実行を防ぐ方法
Slackを「人間が回答する入口」にしたかった
AIに記事を書かせるフローはシンプルに作れます。ただ、完全自動で生成した記事は、既存の情報を整理したものにとどまりがちです。
そこで「Slackに3問のインタビューを送り、回答が届いたらその内容をAIに渡して記事を書かせる」という設計にしました。フローの途中に人間の回答待ちポイントを作る、いわゆる人間介在型の自動化フローです。
これは単なる通知フローではありません。Slackへの投稿はあくまで「質問を届ける手段」であり、スレッドへの返信が来るまでフローを止めておく必要があります。n8nのWebhookを使えば、Slackの返信イベントをトリガーにしてフロー2を起動できます。
ただし、「Webhook URLを設定してSlack返信を受け取る」だけでは実運用で壊れやすいことが分かりました。url_verification、誤送信、品質判定、リトライ対策まで含めないと、安定した承認フローにはなりません。
今回の構成:SlackインタビューからWebhookでフロー2を起動
フロー全体の構成はこうです。
フロー1(Schedule Trigger起点):
- keyword_queue.csvからキーワードを1件取得
- 記事設計・市場調査をLLMで実行
- インタビュー質問3問をSlackチャンネルに投稿
- session.jsonを保存してフロー1終了
フロー2(Webhook起点):
- Slackのスレッド返信をWebhookで受信
- url_verificationなら challengeを返して終了
- リトライヘッダーがあれば停止
- スレッド返信かどうかを確認
- 文字数チェック・LLM品質判定
- session.jsonを読み込んで前段データを復元
- 返信テキストと設計データを統合して記事生成へ
Slackが「人間確認の入口」と「フロー再開のトリガー」を兼ねています。フロー間のデータ保持についてはsession.jsonで状態保持する実装で詳しく書いています。並列ノード取得のMerge設計についてはMergeノードの直接参照設計も参考にしてください。
Slack Events APIのWebhook URLをn8nに向ける
まずSlack App側の設定から始めます。
- Slack APIでAppを作成(または既存のAppを使用)
- 左メニューの「Event Subscriptions」を有効化
- Request URLにn8nのWebhook URLを入力
- Subscribe to bot eventsで
message.channelsを追加 - Bot Token Scopesで必要な権限を付与。必要なスコープは構成によって変わりますが、今回のようにBotがSlackへ質問を投稿しチャンネル内の返信イベントを扱う構成では、投稿用の
chat:writeと、対象チャンネルの履歴を扱うための権限(channels:historyなど)を確認しました
n8n側では、Webhookノードを用意してURLを発行します。HTTPメソッドはPOST、Authentication設定はなしで受けています。この記事では動作確認を優先してAuthentication設定なしで進めていますが、本番運用ではSlackのSigning Secretを使った署名検証や、受信元IPの検証を追加する方が安全です。
受信するイベントは message.channels にします。これによりチャンネル内のメッセージとスレッド返信の両方を受け取れます。
ただしこのままでは、チャンネルのすべての投稿をWebhookが受け取ります。次のステップで不要なイベントを絞り込んでいきます。
詰まり1:url_verificationを返さないとWebhookが有効化できない
最初の詰まりがここでした。
Slack Event SubscriptionsでRequest URLを登録しようとすると、Slackは確認のために url_verification タイプのリクエストを送ってきます。このリクエストに対して、ボディの challenge の値をそのまま返す必要があります。
これを処理しないと、Request URLの欄に「Verified」と表示されず、イベント受信が始まりません。
n8n側の対応はこうです。
Webhookノードの後にIFノードを置き、type === "url_verification" を判定します。
条件:{{ $json.body.type }} equals "url_verification"trueルートでは、Respond to Webhookノードを使って challenge の値をそのまま返します。Slack側が求める形式に合わせて、challengeの値をレスポンス本文として返します。n8nではRespond to Webhookノードで、JSONまたはテキストとしてchallengeを返す設定にします。
{ "challenge": "{{ $json.body.challenge }}" }falseルートは、通常のメッセージ処理フローへ進みます。
この分岐を入れるまで、Slackのリクエスト検証が通らず、イベントが一切届きませんでした。Slack連携で最初に詰まるポイントとして、必ず対応が必要です。
スレッド返信だけを処理対象にする絞り込み設計
message.channels では、チャンネルへの通常投稿もスレッド返信もすべて届きます。
今回処理したいのは「インタビュー質問投稿へのスレッド返信」だけです。不要なイベントを後続処理に流さないよう、絞り込みを入れます。
確認するポイント:
thread_tsが存在するか → スレッド返信かどうかの判定bot_idが含まれていないか → Bot自身の投稿を除外- チャンネルIDが対象チャンネルか → 別チャンネルのイベントを除外
IFノードまたはCodeノードで、これらの条件を順に確認します。
const event = $json.body.event;
// スレッド返信かどうか
if (!event.thread_ts) return false;
// Bot自身の投稿を除外
if (event.bot_id) return false;
// 対象チャンネルかどうか
if (event.channel !== 'C0AV96NMES2') return false;
return true;thread_ts はそのまま session.json との照合にも使います。どのインタビュー投稿への返信かを特定するためです。
詰まり2:誤送信・テスト返信でフロー2が動いてしまった
絞り込み設計を入れた後も、別の問題が出ました。
「OK」「テスト」「確認」など、短い文字だけ送ってもWebhookが起動してフロー2が進んでしまうことです。Slackで動作確認をしていたとき、テスト送信のたびに記事生成フローが動き始めて困りました。
AI記事生成フローでは、Q1〜Q3のインタビュー質問に対して実体験・数値・判断を含む回答が必要です。「了解」の一言では記事に使えるような一次情報になりません。
単純にWebhookを受け取るだけでは、人間介在フローとして機能しないことが分かりました。
文字数チェックとLLM品質判定でふるいにかけた
対策として、2段階のチェックを入れました。
ステップ1:文字数チェック
Codeノードで返信テキストの文字数を確認します。一定文字数(今回は50文字)未満の場合は後続処理に進みません。
const text = $json.body.event.text;
if (text.length < 50) {
throw new Error('回答が短すぎます。Q1〜Q3に対する回答を送ってください。');
}エラーにするかSlackに再回答を促すメッセージを送るかは、運用に応じて選択します。
ステップ2:LLM品質判定
文字数チェックを通過した返信を、LLMに渡して品質を確認します。
Systemプロンプトの方向性:
あなたはインタビュー回答の品質判定担当です。
以下のQ1〜Q3の質問に対して、記事生成に使える一次情報が含まれているかを判定してください。
「具体的な経験・数値・判断の根拠」が含まれていればOKです。
挨拶・確認・感想のみの場合はNGとします。
出力はJSONのみ。前後に説明文・コメント・マークダウンのコードブロックを一切含めないこと。最初の文字が{、最後の文字が}になるように出力する。
{
"is_valid": true/false,
"reason": "判定理由",
"missing_points": ["不足している点"]
}後続のCodeノードでJSON.parseするため、余分な文字が入るとパースエラーになります。このルールはすべてのJSONを返すLLMノードに共通して入れておくことをおすすめします。
is_valid がfalseの場合は、フロー2をそこで止めます。必要であればSlackに再回答を依頼するメッセージを返すことも可能です。
LLM品質判定は完全ではありません。補助的なチェックとして使い、最終的な判断は記事生成後のレビューで補うのが現実的だと感じています。
詰まり3:Slackリトライで同じフローが2回起動した
これが実運用で一番困った問題でした。
Slack Events APIは、Webhookへのリクエストが失敗したと判断すると自動的にリトライします。n8n側の処理に少し時間がかかったり、レスポンスが遅れたりするだけで、同じイベントが複数回送られてきます。
その結果、1つのSlack返信に対して記事生成が2回走ったり、完了通知が重複して送られたりしました。
対策は、リトライリクエストを検出して無視することです。
Slackのリトライリクエストには X-Slack-Retry-Reason というヘッダーが付きます。このヘッダーが存在する場合は、Respond to Webhookノードで即座に200を返して処理を終了します。
この判定は、session.jsonの読み込みやLLM品質判定より前に置く必要があります。重い処理に入った後ではリトライによる二重実行を防ぎにくくなるためです。
n8nのIFノードで確認する条件:
{{ $json.headers['x-slack-retry-reason'] }} is not emptytrueの場合はRespond to Webhookで200を返してフロー終了、falseの場合は通常処理へ進みます。
この対策を入れてから、二重実行の問題はほぼなくなりました。Slack連携の実運用では必須の処理だと思っています。
session.jsonとthread_tsで前段データを復元する
Slack返信だけでは、記事生成に必要なデータが揃いません。
フロー1で作った keyword・slug・記事設計データをフロー2で使うには、session.jsonを読み込む必要があります。thread_ts(返信先メッセージのタイムスタンプ)を使って、対応するsession.jsonを特定して読み込みます。
const threadTs = $json.body.event.thread_ts;
// /files/sessions/session_{threadTs}.json を読み込むsession.jsonの詳細な設計とDockerボリュームの対応関係については、n8nでWebhook後にデータが消える問題をsession.jsonで解決した記事で詳しく書いています。
この設計が向いているケース・向いていないケース
Slack承認フローは万能ではありません。
向いているケース:
- AI生成前に人間の一次情報・経験・判断を挟みたい
- Slackを日常的に使っているチームや個人
- 品質を優先するため多少の待ち時間を許容できる
- 「止める・待つ・再開する」設計を自動化に取り入れたい
- 記事生成・コンテンツ制作・承認ワークフローに応用したい
向いていないケース:
- 完全自動・即時処理が必要な業務
- Slackを使わない環境
- 大量の並列承認を厳密に管理したい場合
- 監査ログや権限管理が厳しい業務フロー
- Slack返信の揺れ(誤字・省略表現など)を許容できない業務
今回はAI記事生成フローへの適用でしたが、承認依頼・進捗報告・補足情報収集など、人間の入力が必要な場面であれば応用できると感じています。
まとめ
n8nのSlack承認フローは、Webhook URLを設定するだけで動くように見えますが、実運用では複数の対策が必要でした。
対応した項目をまとめると:
- url_verificationチャレンジへの対応(必須)
- スレッド返信の絞り込み(通常投稿・Bot投稿を除外)
- 文字数チェックとLLM品質判定(誤送信・薄い回答を除外)
- x-slack-retry-reasonによるリトライ検出・二重実行防止
これらを入れてから、承認フローが安定して動くようになりました。
今回の設計で重要だったのは、Slack連携の技術的な実装だけでなく、どこで人間の判断を挟み、どの条件でフローを再開するかを設計することでした。こうした自動化の判断軸はbizinets.biz側で整理しています。
bizinets.biz
記事を読んで「もっと知りたい」と思ったあなたへ
bizinets.biz では、ビジネスの仕組みづくりを体系的に学べるコンテンツを用意しています。まずは一度、覗いてみてください。
bizinets.biz へ行く →