bbizinets.life
n8nでWebhook後にデータが消える原因とsession.jsonで解決する方法
実践ログ

n8nでWebhook後にデータが消える原因とsession.jsonで解決する方法

結論:n8nはデータを保持しません。session.jsonで外部保存するのが解決策です

n8nでWebhookを挟んだと、前の実行のデータはすべて消えます。

n8nはワークフローの実行をまたいでデータを保持しません。Webhook受信後に別の実行として起動するため、前の実行のデータは参照できない仕様です。

解決策はシンプルです。状態を保持したいデータをJSONファイルとして書き込んでおき、次のフローで読み込むだけです。書き込みはWrite File to Disk、読み込みはRead File(s) From Disk+Extract from Fileの2セットで実装できます。

なぜWebhook後にデータが消えるのか:n8nの実行モデルを理解する

詰まりの原因を理解しておくと、同じ問題が別の場所で起きたときも対処できます。

n8nのワークフローはトリガーごとに独立した実行単位として動きます。Schedule Triggerで起動した実行と、Webhookで起動した実行は、完全に別のプロセスです。

具体的には次のような状況でデータが消えます。

[実行①] Schedule Trigger起動
  → キーワードを取得・記事設計を生成
  → Slackにインタビュー質問を送信
  → 実行①が終了(ここでデータは消える)

[実行②] Webhook受信で起動
  → Slackの返信を受け取る
  → 実行①で取得したキーワードが必要
  → しかし実行②は実行①のデータを持っていない

実行①と実行②の間には時間差があり、人間がSlackに返信するまで数分から数時間かかることもあります。n8nにはこの「待機中にデータを持ち続ける」仕組みがありません。

これはn8nのバグではなく仕様です。 ステートレスな設計になっているため、状態の保持は開発者が外部ストレージで実装する必要があります。

この構造を理解した上で、session.jsonによる解決策を実装します。

解決策:session.jsonに書き込んで次のフローで読み込む

解決の考え方はシンプルです。

[実行①] Schedule Trigger起動
  → データを取得・処理
  → session_{ts}.json に保存
  → Slackにメッセージ送信(tsを記録)
  → 実行①終了

[実行②] Webhook受信で起動
  → Slackの thread_ts を取得
  → session_{thread_ts}.json を読み込む
  → 実行①のデータを復元して処理を継続

tsとthread_tsを一致させることで、「どのsession.jsonを読み込むか」を特定できます。

実装は以下の2ステップです。

  1. 書き込み(実行①の末尾): Write File to DiskノードでJSONを保存
  2. 読み込み(実行②の冒頭): Read File(s) From Disk+Extract from FileノードでJSONを読み込む

書き込み設定:Write File to Diskノードの実装

実行①の末尾(Slackへの送信ノードの直後)にWrite File to Diskノードを追加します。

保存するデータの準備

まずCodeノードで保存するデータをバイナリに変換します。

// JSONをバイナリ形式に変換してファイル保存用に準備します
const ts = $('Send interview').first().json.message.ts;

const sessionData = {
  ts: ts,
  keyword: $('00後の整形').first().json.keyword,
  slug: $('00後の整形').first().json.slug,
  context_01: $('01後の整形').first().json.design,
};

const jsonString = JSON.stringify(sessionData, null, 2);
const base64 = Buffer.from(jsonString, 'utf8').toString('base64');

return [{
  json: { ts },
  binary: {
    data: {
      data: base64,
      mimeType: 'application/json',
      fileName: `session_${ts}.json`,
      fileExtension: 'json',
    }
  }
}];

tsはSlackへのメッセージ送信ノードのレスポンスから取得します。このtsがsession.jsonのファイル名になり、後でthread_tsと一致させるキーになります。

Write File to Diskノードの設定

  • Operation: Write File to Disk
  • File Path and Name: /files/sessions/session_{{ $json.ts }}.json
  • Input Binary Field: data

/files/はn8nのDockerコンテナ内のパスです。docker-compose.ymlで/root/n8n/files:/filesとホストのディレクトリをマウントしている場合、実際のファイルは/root/n8n/files/sessions/に保存されます。

読み込み設定:Read File(s) From Disk+Extract from Fileノードの実装

実行②(Webhook受信後)でsession.jsonを読み込みます。ここで詰まりやすい点があります。

Read File(s) From Diskノードの設定

  • Operation: Read File(s) From Disk
  • File(s) Selector: /files/sessions/session_{{ $json.thread_ts }}.json

$json.thread_tsはWebhookで受信したSlackイベントのevent.thread_tsです。Slackがスレッドに返信したとき、親メッセージのtsがthread_tsとして入ってきます。

Extract from Fileノードが必須な理由

n8nのbinaryDataMode=filesystemの環境では、Read File(s) From Diskの出力バイナリを直接Codeノードで読み取ることができません。

binary.data.dataの中身が"filesystem-v2"という参照ポインタになっており、実データはディスク上に別管理されています。Codeノードからはこの参照を解決できないため、専用のExtract from FileノードをRead File(s) From Diskの直後に必ず挟む必要があります。

詳細は別記事「n8n binaryToStringエラーの原因と解決法」をご覧ください。

Extract from Fileノードの設定

  • Operation: Convert to JSON
  • Source Key: data

これで$json.fileContentにsession.jsonの内容がオブジェクトとして展開されます。

後続のCodeノードでの参照方法

const session = $('Extract from File').first().json.fileContent;

return [{
  json: {
    keyword: session.keyword,
    slug: session.slug,
    context_01: session.context_01,
    reply_text: $('スレッド返答受信').first().json.reply_text,
  }
}];

これで実行①で保存したデータと実行②で受け取ったSlack返信テキストを統合できます。

thread_tsとファイル名を一致させる仕組み

session.jsonの設計で最も重要なのは「どのsessionファイルを読み込むか」の特定方法です。

Slackのスレッド機能では、親メッセージのts(タイムスタンプ)がスレッドのIDになります。

親メッセージの ts:1777765978.702799
  ↓
このスレッドへの返信には全て thread_ts: 1777765978.702799 が付く

この仕組みを利用して、session.jsonのファイル名をsession_{親メッセージのts}.jsonにすることで、Webhook受信時のthread_tsでファイルを特定できます。

書き込み時:session_1777765978.702799.json
読み込み時:session_{{ $json.thread_ts }}.json
           → session_1777765978.702799.json を読み込む

tsとthread_tsが一致する前提が崩れると「No file(s) found」エラーになります。後述のテスト時の注意点を参照してください。

テスト時の注意点とハマりポイント

実装後のテストで詰まりやすいポイントを整理します。

ハマりポイント①:ピン止めデータのthread_tsが古い

n8nのEditorでWebhookノードをテストするとき、本番URLへのリクエストはEditorには届きません。そのためWebhookノードの出力をピン止め(Pin data)してテストするのが一般的です。

このとき、ピン止めデータのthread_tsが実際に存在するsession.jsonのtsと一致していない場合、ファイルが見つからずエラーになります。

対処:ピン止めデータのthread_tsを、/root/n8n/files/sessions/に実際に存在するsession.jsonのファイル名(tsの値)と一致させてからテストを実行する。

ハマりポイント②:Extract from FileノードをRead File後に挟んでいない

binaryDataMode=filesystemの環境では、Read File(s) From Diskの直後にExtract from Fileノードを挟まないとデータを読み取れません。Codeノードで直接読もうとすると以下のエラーが出ます。

  • binaryToString → Unknown error
  • Buffer.from(binary.data.data, 'base64') → 文字化け("filesystem-v2"がbase64ではない)

必ずExtract from Fileノードを間に入れてください。

ハマりポイント③:tsの取り違え

session.jsonのファイル名に使うtsには2種類あります。

値 | 取得元 | 用途
--- | --- | ---
`message.ts` | Slack送信ノードのレスポンス | session.jsonのファイル名
`event.thread_ts` | Webhookのイベントデータ | session.jsonの読み込みキー

書き込み時にmessage.tsではなくts(実行時刻)や別の値を使ってしまうと、読み込み時にthread_tsと一致しなくなります。Slackへの送信ノードのレスポンス構造を確認してから実装してください。

ハマりポイント④:sessionsディレクトリが存在しない

Write File to Diskノードは指定したパスのディレクトリが存在しない場合にエラーになります。初回実行前にHetznerサーバーにSSH接続して以下のコマンドでディレクトリを作成してください。

mkdir -p /root/n8n/files/sessions

この後どうなったか

session.jsonの実装が完成してからはどう変わったか、少しだけ紹介します。Webhook待機を挟む非同期フローが安定して動くようになりました。一番嬉しかったのは、外出先でSlackに返信するだけで後続の処理が正しく再開されるのを初めて確認したときです。

session.jsonのパターンはSlack承認フロー以外にも応用できます。「長時間かかる処理の途中で人間の確認を挟む」「複数のWebhookをまたいで状態を共有する」といったユースケース全般に使えます。一度設計パターンを理解すると、同種の問題にすぐ対処できるようになります。

よくある質問

session.jsonの実装でよく受ける質問をまとめました。あなたが詰まっているのはどのステップですか?

Q. session.jsonはどこに保存すればいいですか?

n8nのDocker環境では/files/sessions/(ホスト側は/root/n8n/files/sessions/)が推奨です。docker-compose.ymlのvolumes設定で/root/n8n/files:/filesとマウントされていれば、このパスにアクセスできます。

Q. session.jsonを使わず、n8nのグローバル変数やStatic Dataで代替できませんか?

Static Data(this.getWorkflowStaticData('global'))はCodeノード内で使えますが、n8n 2.17.7ではTask Runnerモードが有効になっておりthis.getWorkflowStaticDataが機能しない場合があります。session.jsonはシンプルで確実なため、現時点では最も安定した方法です。

Q. session.jsonが溜まっていきますが、削除の仕組みは必要ですか?

毎日1件ずつ生成される場合、1年で365ファイルです。5KBとして約1.8MBなのでストレージ上の問題にはなりにくいですが、古いファイルを定期的に削除するスクリプトをHetznerのcronで動かすとクリーンに保てます。

まとめ

n8nのワークフローをまたいでデータを保持するには、外部ファイルへの保存が必須です。

  • 書き込み: Slack送信直後にWrite File to Diskでsession_{ts}.jsonを保存
  • 読み込み: Read File(s) From Disk+Extract from FileでJSONを取得
  • 紐付け: Slack tsをファイル名にして、thread_tsで読み込む

今日できる一歩: まずmkdir -p /root/n8n/files/sessionsでディレクトリを作成し、Write File to Diskノードを1つ追加してsession.jsonが正しく保存されることを確認してみてください。読み込みの実装はその後で大丈夫です。

今回のsession.json設計で最も時間がかかったのは「どのデータをsessionに持たせてどこで処理するか」の切り分けでした。この設計判断を体系的に整理したい方は、bizinets.bizの判断軸ページが参考になると思います。

bizinets.biz

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

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

bizinets.biz へ行く →

関連記事