bbizinets.life
n8nとSlackで承認フローを作る方法【Webhook・thread_ts・session管理の実装手順】
実践ログ

n8nとSlackで承認フローを作る方法【Webhook・thread_ts・session管理の実装手順】

結論:Slack承認フローはWebhook・thread_ts・sessionの3点で成立します

n8nでSlackを使った承認フロー(人間がSlackに返信したら処理が再開される仕組み)を作りたい方に向けて、実装手順を解説します。

この仕組みは3つの技術要素で成立します。Slack Event SubscriptionsのWebhook登録・thread_tsによるスレッド返信の特定・session.jsonによるコンテキストの保持です。

この3点を順番に実装するだけで、n8nのワークフローに人間の判断を介在させる非同期の承認フローが完成します。

全体の構成を先に把握しておきましょう

実装を始める前に、フローの全体像を把握しておくと詰まりにくくなります。

承認フローは以下の流れで動きます。

[n8n] スケジュール起動
  ↓
[n8n] Slackにメッセージ送信(インタビュー質問など)
  ↓ ← ここで一時停止
[人間] Slackのスレッドに返信する
  ↓
[Slack] Webhook経由でn8nに通知
  ↓
[n8n] Webhookノードがイベントを受信
  ↓
[n8n] IFノードでchallenge/event_callbackを分岐
  ↓
[n8n] thread_tsで返信を特定・session.jsonを読み込む
  ↓
[n8n] 後続処理(記事生成・投稿など)を再開

一時停止と再開の間に人間の判断が入るのが承認フローの本質です。n8nは非同期でWebhookを待ち受けるため、人間がいつ返信しても正しく処理が再開されます。

実装は大きく3つのフェーズに分かれます。

  1. Slack Event Subscriptionsの設定(Webhook登録とVerified化)
  2. n8n側のWebhookノードとchallenge処理の実装
  3. thread_ts管理とsession.jsonによるコンテキスト保持

Slack Event Subscriptions設定とVerified化の手順

まずSlack側の設定から始めます。Slack APIのダッシュボードにアクセスして、アプリの設定を行います。

Event Subscriptionsの有効化

  1. api.slack.com/apps にアクセスする
  2. 対象アプリを選択(なければ「Create New App」で作成)
  3. 左メニューの「Event Subscriptions」を開く
  4. 「Enable Events」をONにする
  5. 「Request URL」にn8nのWebhook URLを入力する

n8nのWebhook URLは以下の形式です。

https://n8n.あなたのドメイン/webhook/slack-thread-reply

重要: Request URLを入力した瞬間にSlackがVerification requestを送信します。この時点でn8nのWebhookノードが稼働していないと「Verified」にならずに進めません。n8nでワークフローを先に有効化してから、SlackのRequest URLを入力する順番で進めてください。

Subscribe to Bot Eventsの設定

Request URLがVerifiedになったら、イベントを追加します。

「Subscribe to Bot Events」セクションで「Add Bot User Event」をクリックして以下を追加します。

  • message.channels(パブリックチャンネルのメッセージ)

設定が完了したら「Save Changes」をクリックして保存します。最後にアプリをワークスペースに再インストールする必要がある場合は、画面上部のバナーの指示に従ってください。

最初の壁:challengeレスポンス処理をIFノードで実装する

Slack Event Subscriptionsを設定したとき、最初に詰まりやすいのがこのchallenge処理です。

SlackはRequest URLを登録したとき、そのURLが正当なエンドポイントかどうかを確認するためにurl_verificationイベントを送信します。このイベントに対して、受け取ったchallengeの値をそのままレスポンスとして返さなければ、Verifiedになりません。

n8nでこれを処理するには、IFノードで分岐させます。

IFノードの設定

Webhookノードの後にIFノードを追加して、以下の条件で分岐させます。

条件:{{ $json.body.type }} が "url_verification" と等しい
True  → challengeをレスポンスとして返す(処理終了)
False → event_callbackとして処理を継続する

True側(challenge返却)の実装

True側には「Respond to Webhook」ノードを追加して以下を設定します。

  • Response Code:200
  • Response Body:{{ $json.body.challenge }}

これでSlackのVerification requestに対して正しく応答できます。

False側(event_callback処理)の実装

False側が実際のSlackイベント処理になります。ユーザーがスレッドに返信したときのイベントがここに流れてきます。

イベントの構造は以下のようになっています。

{
  "type": "event_callback",
  "event": {
    "type": "message",
    "text": "ユーザーの返信テキスト",
    "thread_ts": "1234567890.123456",
    "ts": "1234567890.234567",
    "user": "U0xxxxxxxx"
  }
}

event.thread_tsがスレッドの親メッセージのタイムスタンプです。これがsession.jsonのファイル名と一致する値になります。

thread_tsでスレッド返信を特定する仕組み

Slackのスレッド機能では、1つのメッセージに対する返信がthread_tsというフィールドで紐付けられます。

n8nがSlackにメッセージを送信したとき、そのメッセージのタイムスタンプ(ts)がスレッドのIDになります。ユーザーがそのスレッドに返信すると、Webhookイベントのevent.thread_tsにその値が入ってきます。

つまり、Slackへの送信時の`ts`とWebhookイベントの`thread_ts`が一致すれば、そのイベントが自分のワークフローへの返信だと特定できます。

スレッド返信受信のCodeノード

IFノードのFalse側にCodeノードを追加して、以下のコードで返信データを抽出します。

// $input.first() はWebhookの入力データを取得する記法です
const body = $input.first().json.body;
const event = body.event;

// スレッド返信のみ処理(通常メッセージは除外)
if (!event.thread_ts) {
  return [];
}

// ボットの自己メッセージは除外
if (event.bot_id) {
  return [];
}

return [{
  json: {
    thread_ts: event.thread_ts,
    reply_text: event.text,
    user: event.user,
  }
}];

event.thread_tsが存在しない場合はスレッド返信ではないため空配列を返して処理を止めます。ボットが自分のメッセージに反応して無限ループになるのを防ぐため、bot_idチェックも入れておくことをおすすめします。

session.jsonでコンテキストを保持・再開する

承認フローで最も重要な設計上の課題は「一時停止中にコンテキストをどこに保存するか」です。

n8nのワークフローは実行ごとに独立しています。Slackへのメッセージ送信時の実行と、Webhook受信時の実行は別々のトリガーで起動するため、前の実行で持っていたデータは引き継がれません。

この問題をsession.jsonで解決します。

session.jsonに保存するデータ

Slackにメッセージを送信した直後に、後続処理で必要なデータをsession.jsonとして保存します。

{
  "ts": "1234567890.123456",
  "keyword": "副業 疲れた やめたい",
  "slug": "fukugyo-tsukareta-yametai",
  "context_01": { ... }
}

tsはSlack送信時のメッセージタイムスタンプです。Webhook受信時のthread_tsとこの値が一致するため、ファイル名として使います。

session.jsonの書き込み

n8nの「Write File to Disk」ノードを使って以下のパスに保存します。

/files/sessions/session_{{ $json.ts }}.json

$json.tsはSlackへのメッセージ送信ノードのレスポンスに含まれるmessage.tsの値です。

session.jsonの読み込み

Webhook受信後、thread_tsを使って該当するsession.jsonを読み込みます。

/files/sessions/session_{{ $json.thread_ts }}.json

注意: n8nがbinaryDataMode=filesystemで動作している場合、Read File(s) From Diskノードの後に「Extract from File」ノードを挟む必要があります。詳細は別記事「n8n binaryToStringエラーの原因と解決法」をご覧ください。

ピン止めデータを使ったテスト方法

本番の承認フローをテストするとき、毎回Slackに実際のメッセージを送って返信するのは手間がかかります。n8nのEditor上では「ピン止め(Pin data)」機能を使うと効率的にテストできます。

ピン止めデータとは

n8nのノードを実行したときの出力データを固定して、以降のテストでその固定データを入力として使う機能です。

Webhookノードは本番環境ではSlackからのPOSTリクエストを受け取りますが、Editor上でテストするときはテスト用URLしか使えません。本番のSlack Event SubscriptionsはProduction URLに向いているため、Editorのテスト実行ではWebhookノードが受信できないのです。

ピン止めデータの設定方法

  1. 一度本番フローを走らせてWebhookノードが実際のSlackイベントを受信した実行ログを確認する
  2. Webhookノードの出力データをコピーする
  3. Editorでそのノードを右クリック → 「Pin data」を選択してデータを貼り付ける

これで以降のEditor上のテストでは、ピン止めデータが入力として使われます。後続ノードを順番に「Execute step」で実行して動作確認ができます。

テスト時の注意点

ピン止めデータに含まれるthread_tsと、実際にHetzner上に保存されているsession.jsonのファイル名が一致していないとファイルが見つからないエラーになります。テスト時はピン止めデータのthread_tsを、実際に存在するsession.jsonのファイル名(tsの値)と一致させてから実行してください。

ハマりポイントまとめ

実装中に詰まりやすいポイントを整理しておきます。あなたはどのパターンに当てはまっていますか?

ハマりポイント①:Webhookノードを有効化する前にSlackのRequest URLを入力してしまう

SlackはRequest URLを入力した瞬間にVerification requestを送信します。この時点でn8nのWebhookノードが稼働していないと応答できずにVerifiedになりません。

対処:n8nでワークフローをActivate(本番有効化)してからSlackのRequest URLを入力する。

ハマりポイント②:challenge処理を実装しないまま進める

challenge処理なしでもEvent Subscriptionsページ自体は閉じられますが、Webhookが正常に機能しません。IFノードでurl_verificationを分岐させてchallengeを返すところまで必ず実装してください。

ハマりポイント③:thread_tsとsession.jsonのtsが一致しない

session.jsonの保存時に$json.ts(Slack送信時のts)でファイル名を作り、読み込み時に$json.thread_ts(Webhook受信時のthread_ts)でファイルを探します。送信時のtsがそのままスレッドのthread_tsになるため、設計上は一致するはずです。テスト時はピン止めデータのthread_tsを実際のファイル名に合わせる必要があります。

ハマりポイント④:ボットの自己メッセージが無限ループを起こす

n8nがSlackに送信したメッセージが再びWebhookで受信されてループします。Codeノードでevent.bot_idが存在する場合は処理を止める分岐を必ず入れてください。

この後どうなったか

この承認フローを実装してからはどう変わったか、少しだけ紹介します。外出先でスマートフォンのSlackアプリからインタビューに答えるだけで記事生成が自動的に再開されるようになりました。正直、最初にうまく動いたときは思わず声が出るくらい嬉しかったです。

session.jsonによる状態管理の設計が最も時間がかかりましたが、この仕組みを一度作ると、他の非同期処理にも同じパターンで応用できます。承認フロー以外にも「長い処理の途中で人間の確認を挟む」ユースケース全般に使えます。

よくある質問

承認フロー実装でよく受ける質問をまとめました。あなたが詰まっているのはどのポイントですか?

Q. Slack Appのスコープは何を追加すればいいですか?

最低限必要なスコープはchannels:history(チャンネルのメッセージ読み取り)とchat:write(メッセージ送信)です。Event SubscriptionsでBot Eventsを有効にするにはchannels:historyが必要です。

Q. Webhookノードのパスはどう決めればいいですか?

任意の文字列で構いません。/webhook/slack-thread-replyのように用途が分かる名前にしておくと管理しやすいです。SlackのEvent SubscriptionsのRequest URLにはこのパスを含めたフルURLを入力します。

Q. Slackの無料プランでも動作しますか?

はい、動作します。Slack Event Subscriptionsは無料プランでも利用できます。ただしメッセージの保存期間に制限があるため、古いスレッドへの返信が取得できない場合があります。

まとめ

n8nとSlackで承認フローを実装するには、以下の3点を押さえてください。

  • Webhook+challenge処理:Slack Event SubscriptionsのVerified化とIFノードによる分岐
  • thread_ts管理:スレッド返信の特定とボット自己メッセージの除外
  • session.json:一時停止中のコンテキスト保持と再開時の読み込み

今日できる一歩: まずSlack APIのダッシュボードでEvent Subscriptionsを有効化して、n8nのWebhookノードをActivateしてみてください。Verifiedになれば第一フェーズは完了です。

今回実装した承認フローの設計で一番時間がかかったのは、どの処理をn8nノードに任せてどこを人間が判断するかの切り分けでした。この「何をどこに任せるか」の判断軸を体系的に整理したい方は、bizinets.bizの判断軸ページが参考になると思います。

ここまでの内容で、承認フローの実装手順と詰まりポイントは整理できたと思います。
ただし、このままだと自分のワークフロー全体でどこに人間の判断を介在させるべきかは判断できません
なぜなら、「人間介在点をどう設計するか」の判断軸が、まだ整理されていないからです。

判断軸の整理はこちら

bizinets.biz

記事を読んで「もっと知りたい」と思ったあなたへ

bizinets.biz では、ビジネスの仕組みづくりを体系的に学べるコンテンツを用意しています。まずは一度、覗いてみてください。

bizinets.biz へ行く →

関連記事