n8n + LINE + Supabaseでスマホから承認できるワークフロー自動化を実装する

AIが下書きしたコンテンツ、結局PCを開かないと承認できていませんか?LINEに「OK」と返すだけで次の処理が走る仕組みを、n8n+Supabaseで30分以内に作れます。外出先でスマホをさっと取り出して、通知を確認してボタンをタップするだけ。それだけで自動化の続きが動き出す世界、一緒に実装してみましょう。

目次

フェーズ1:なぜ「承認フロー」がボトルネックになるのか

n8nで自動化を組んでいると、ある壁にぶつかります。データ収集・加工・投稿といった処理はノードをつなぐだけで自動化できるのに、「人間がチェックして承認する」というステップだけがどうしてもPC依存になってしまう問題です。

自動化が進んでも「人間の確認」だけがPC依存になる構造的理由

n8n単体でできることは非常に多いですが、インタラクティブな(双方向の)承認ステップには本質的な弱点があります。n8nのワークフローは基本的に「トリガーが来たら一方向に流れる」設計です。途中で人間の判断を待つ「待機状態」を作ることが、標準機能だけでは難しいのです。

たとえば「AIが生成したブログ下書きを確認してからWordPressに投稿したい」というケースを考えてみましょう。n8nでAI生成までは自動化できても、「確認・承認」の部分でワークフローを一時停止させ、人間の返答を待って再開する仕組みを作るには工夫が必要です。多くの方が「とりあえずメールで下書きを受け取って、PCを開いて確認して、手動で投稿ボタンを押す」という半自動化で妥協しているのではないでしょうか。

外出中に承認できない「具体的な損失シーン」

個人事業主やフリーランスの方が特に痛感するのが、以下のようなシーンです。

  • クライアント訪問中にAIが記事を生成完了したが、投稿承認はPCに戻ってから
  • 移動中にSNS投稿の自動生成が終わったが、内容確認できずタイムラグが発生
  • 週末の外出中に定期レポートが完成したが、月曜朝まで後続処理が止まったまま
  • カフェで作業できる環境がない場面で、承認待ちのタスクが積み上がっていく

これらはどれも「承認さえできれば自動で続きが動く」のに、PCがないという一点だけで止まってしまうケースです。時間的なロスはもちろん、「せっかく自動化したのに結局手が離れない」というストレスも積み重なります。

n8n単体では解決しにくいインタラクティブな承認ステップの課題

n8nには「Wait」ノードという機能があり、一定時間やWebhookの受信まで処理を一時停止させることはできます。ただし、これだけでは「どこで・どうやって承認するか」というUIの問題が解決しません。メールやSlackへの通知は可能でも、スマホから直感的に「承認」「却下」を返せる仕組みを作るには、追加の設計が必要です。

そこでハナが今回ご提案するのが、Supabaseを状態管理の中核に置き、LINEを承認UIとして使うアーキテクチャです。日本のビジネスパーソンがほぼ全員使っているLINEをUIにすることで、専用アプリのインストール不要・学習コストゼロで承認フローを実現できます。

フェーズ2:n8n × LINE × Supabaseの役割分担を設計する

3ツールの責務を明確化する

このシステムを構成する3つのツールには、それぞれ明確な役割があります。役割を混在させないことが、後のメンテナンスをシンプルに保つコツです。

🔧 各ツールの責務

  • n8n(オーケストレーター):全体のフローを制御する司令塔。リクエスト生成、LINE通知送信、返信受信、後続処理の実行を担当
  • Supabase(状態管理DB):承認リクエストの状態(pending/approved/rejected)を永続化。Webhookで状態変化をn8nに通知する
  • LINE(承認UI):ユーザーへの通知と承認・却下の入力インターフェース。FlexメッセージでリッチなUIを提供

承認フローの全体アーキテクチャ

全体の流れを整理すると、以下のようになります。

  1. リクエスト生成:n8nのワークフローがAI生成コンテンツなどを元に承認リクエストを作成
  2. DB保存:SupabaseのApproval_requestsテーブルにステータス「pending」でレコードを挿入
  3. LINE通知:n8nがLINE Push APIを呼び出し、承認ボタン付きFlexメッセージを送信
  4. 返信受信:ユーザーがLINEで「承認」または「却下」を返信→n8nのWebhookノードが受信
  5. ステータス更新:n8nがSupabaseのレコードをapproved/rejectedに更新
  6. 後続処理トリガー:Supabaseのトリガー関数がステータス変更を検知→n8nの後続フローが起動

このアーキテクチャの肝は、SupabaseがシステムのStateを一元管理している点です。n8nのワークフローが複数動いていても、Supabaseを見れば「どのリクエストが今どの状態にあるか」が一目瞭然です。

Supabase WebhookをトリガーとしてのメリットとSupabase設計上の注意点

Supabaseには、テーブルのレコード変化をHTTPリクエストで外部に通知する「Database Webhooks」機能があります。これをn8nのWebhookノードのエンドポイントに向けることで、「Supabaseのステータスが変わったら自動的にn8nが動く」という連携が実現します。

設計上の注意点として、Supabase WebhookはRow Level Security(RLS)の影響を受けないことを覚えておきましょう。Webhookはサービスロールで動作するため、RLSを設定していてもWebhookは正常に発火します。一方、n8nからSupabaseのAPIを叩く際はアノン(匿名)キーかサービスロールキーを適切に使い分けてください。承認リクエストの更新にはサービスロールキーを推奨します。

また、n8nとSupabaseの連携については、Supabase公式のn8nインテグレーションページでも詳細が確認できます。実装前に一度目を通しておくと安心です。

フェーズ3:実装手順ステップバイステップ

では実際に手を動かしていきましょう。所要時間は慣れた方で30分、初めての方でも1時間あれば完成できます。

STEP1:Supabaseでapproval_requestsテーブルを作成する

まずSupabaseのダッシュボードにログインし、「SQL Editor」を開いてください。以下のSQLを実行することで、テーブル・トリガー関数・Webhookトリガーがすべて設定されます。

① Supabase: approval_requestsテーブル作成SQL

承認リクエストを管理するテーブルを作成します。statusカラムでpending/approved/rejectedを管理し、line_user_idで通知先を紐付けます。

CREATE TABLE approval_requests (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  title TEXT NOT NULL,
  content TEXT NOT NULL,
  line_user_id TEXT NOT NULL,
  status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected')),
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE OR REPLACE FUNCTION notify_approval_change()
RETURNS TRIGGER AS $$
BEGIN
  PERFORM pg_notify(
    'approval_requests_change',
    json_build_object(
      'id', NEW.id,
      'title', NEW.title,
      'content', NEW.content,
      'line_user_id', NEW.line_user_id,
      'status', NEW.status,
      'operation', TG_OP
    )::text
  );
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER approval_requests_trigger
AFTER INSERT OR UPDATE ON approval_requests
FOR EACH ROW EXECUTE FUNCTION notify_approval_change();

CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = NOW();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER set_updated_at
BEFORE UPDATE ON approval_requests
FOR EACH ROW EXECUTE FUNCTION update_updated_at();


SQL実行後、Supabaseダッシュボードの「Table Editor」でapproval_requestsテーブルが作成されていることを確認してください。次に「Database」→「Webhooks」からDatabase Webhookを作成し、approval_requestsテーブルのUPDATEイベントをn8nのWebhookエンドポイントURLに向けて設定します。

RLS(Row Level Security)については、今回はn8nがサービスロールキーでアクセスするため、RLSを有効化した場合でもサービスロールはすべてのポリシーをバイパスします。本番環境では適切なポリシーを追加することを推奨します。

STEP2:LINE Messaging APIのチャネルを作成する

LINE Developersコンソール(developers.line.biz)にアクセスし、以下の手順でチャネルを作成します。

  1. 「プロバイダーを作成」→任意の名前を入力
  2. 「新しいチャネルを作成」→「Messaging API」を選択
  3. チャネル名・説明・業種を入力して作成
  4. 「チャネル基本設定」タブでチャネルシークレットをコピー(後でn8nの環境変数に設定)
  5. 「Messaging API設定」タブでチャネルアクセストークン(長期)を発行してコピー
  6. Webhook URLにn8nのWebhookノードのURLを入力し「検証」をクリック
  7. 「Webhookの利用」をONにする

n8nの環境変数(.envファイルまたはn8nの設定画面)に以下を追加してください。

LINE_CHANNEL_SECRET=(チャネルシークレット)
LINE_CHANNEL_ACCESS_TOKEN=(チャネルアクセストークン)


こちらも参考に→ LINEとは?AI副業自動化での活用法【gpirot.com実践例】では、LINE Messaging APIをAI自動化に活用する実践的なアイデアが紹介されています。

STEP3:n8nで「承認リクエスト生成ワークフロー」を構築する

n8nで新しいワークフローを作成します。このワークフローは「承認が必要なコンテンツが生成されたとき」にトリガーされ、Supabaseへの保存とLINE通知を行います。

ノード構成:

  1. トリガーノード:Webhookノード(または任意のトリガー。AIノードの後段に接続)
  2. Supabaseノード(INSERT):approval_requestsテーブルにレコードを挿入。titleにコンテンツタイトル、contentに本文、line_user_idに通知先のLINEユーザーIDを設定
  3. HTTP Requestノード(LINE Push API):承認ボタン付きFlexメッセージを送信

LINE Push APIのエンドポイントは https://api.line.me/v2/bot/message/push です。HTTPヘッダーに Authorization: Bearer {LINE_CHANNEL_ACCESS_TOKEN} を設定してください。

④ LINE: 承認ボタン付きFlexメッセージ(HTTP Requestボディ)

n8nのHTTP RequestノードでLINE Push APIに送信するボディです。承認・却下ボタンにはpostbackではなくメッセージアクションを使い、APPROVE:{id}形式のテキストを返信させます。

{
  "to": "={{ $json.line_user_id }}",
  "messages": [
    {
      "type": "flex",
      "altText": "承認リクエストが届いています",
      "contents": {
        "type": "bubble",
        "size": "mega",
        "header": {
          "type": "box",
          "layout": "vertical

※ 上記のFlexメッセージJSONは実装時に完全なJSONを組み立ててください。headerの後にbody(タイトル・コンテンツテキスト表示)とfooter(承認・却下ボタン)を追加します。承認ボタンのアクションは "type": "message", "text": "APPROVE:{{ $json.id }}"、却下ボタンは "type": "message", "text": "REJECT:{{ $json.id }}" と設定します。

STEP4:n8nで「LINE返信受信ワークフロー」を構築する

LINEからの返信を受け取り、Supabaseのステータスを更新する別ワークフローを作成します。

ノード構成:

  1. Webhookノード:LINE Messaging APIのWebhook URLとして設定したエンドポイント。HTTP Methodは「POST」
  2. Codeノード(署名検証+メッセージ抽出):以下のコードを使用
  3. IFノード(スキップ判定){{ $json.skip }}がtrueの場合は処理を終了
  4. IFノード(承認/却下分岐):actionがAPPROVEかREJECTかで分岐
  5. Supabaseノード(UPDATE):各分岐でステータスを更新
  6. 後続処理ノード:承認時のみWordPress投稿・SNS投稿などを実行

② n8n: LINE Webhook署名検証 + メッセージ抽出(Code Node)

LINEからのWebhookリクエストを受信後、x-line-signatureヘッダーを検証してなりすましを防ぎます。検証通過後にreplyTokenとメッセージテキストを抽出します。

const crypto = require('crypto');

const channelSecret = $env.LINE_CHANNEL_SECRET;
const body = $input.first().json.body;
const rawBody = JSON.stringify(body);
const signature = $input.first().json.headers['x-line-signature'];

const expectedSignature = crypto
  .createHmac('SHA256', channelSecret)
  .update(rawBody)
  .digest('base64');

if (signature !== expectedSignature) {
  throw new Error('Invalid LINE signature');
}

const event = body.events[0];

if (!event || event.type !== 'message' || event.message.type !== 'text') {
  return [{ json: { skip: true } }];
}

const text = event.message.text.trim();
const replyToken = event.replyToken;
const lineUserId = event.source.userId;

const parts = text.split(':');
const action = parts[0];
const requestId = parts[1];

return [{
  json: {
    action,
    requestId,
    replyToken,
    lineUserId,
    rawText: text,
    skip: false
  }
}];


③ n8n: IFノード 承認/却下ステータス分岐の条件設定

LINEから返信されたアクション文字列(APPROVEまたはREJECT)を判定して後続処理を分岐させます。n8n IFノードのConditions設定をJSONで表現したものです。

{
  "conditions": {
    "options": {
      "caseSensitive": false,
      "leftValue": "",
      "typeValidation": "strict"
    },
    "conditions": [
      {
        "id": "condition-approve",
        "leftValue": "={{ $json.action }}",
        "rightValue": "APPROVE",
        "operator": {
          "type": "string",
          "operation": "equals"
        }
      }
    ],
    "combinator": "and"
  }
}


IFノードのtrue出力側にSupabaseのUPDATEノードを接続し、statusapprovedに更新します。false出力側では同様にrejectedを設定します。Supabase Nodeの設定値は以下を使用してください。

/* IFノード true側 (承認) - Supabase Node設定 */
Operation: Update
Table: approval_requests
Filters:
  Column: id
  Value: ={{ $json.requestId }}
Fields to Update:
  status: approved

/* IFノード false側 (却下) - Supabase Node設定 */
Operation: Update
Table: approval_requests
Filters:
  Column: id
  Value: ={{ $json.requestId }}
Fields to Update:
  status: rejected


STEP5:Supabase Webhookで承認ステータス変更を検知してn8nをトリガーする

STEP1で作成したトリガー関数は pg_notify を使っています。SupabaseのDatabase Webhooksを使ってこれをHTTPリクエストに変換し、n8nの別ワークフローのWebhookエンドポイントに送信します。

Supabaseダッシュボードの「Database」→「Webhooks」→「Create a new hook」から設定します。

  • Name:approval-status-change
  • Table:approval_requests
  • Events:UPDATE
  • Type:HTTP Request
  • URL:n8nの後続フロートリガー用WebhookノードのURL
  • HTTP Headers:必要に応じて認証ヘッダーを追加

n8n側では「後続フロートリガー用ワークフロー」を作成し、Webhookノードで受信後に {{ $json.record.status }}approved の場合のみ後続処理(WordPress投稿・メール送信など)を実行するよう設定します。

詳しくは→ Webhook and Supabase: Automate Workflows with n8n(n8n公式)でも連携パターンが紹介されています。セルフホスト環境での設定については セルフホストのn8nとSupabaseを連携させてみよう! も参考になります。

フェーズ4:動作確認・エラー対処と応用パターン

テスト手順:実際に動かして確認する

実装が完了したら、以下の手順でエンドツーエンドのテストを行いましょう。

  1. 承認リクエスト生成ワークフローを手動実行:n8nのワークフロー画面で「Execute Workflow」をクリック。Supabaseにpendingのレコードが挿入され、LINEにFlexメッセージが届くことを確認
  2. LINEで「APPROVE:{id}」を送信:Flexメッセージの承認ボタンをタップ(またはテキストで直接送信)。n8nのLINE返信受信ワークフローの実行ログを確認
  3. Supabaseでレコードを確認:Table Editorでapproval_requestsテーブルを開き、該当レコードのstatusが「approved」に変わっていることを確認
  4. 後続処理の実行を確認:Supabase Webhookが発火してn8nの後続フローが動いているか、実行ログで確認
  5. 却下テスト:同様に「REJECT:{id}」を送信し、statusが「rejected」になること・後続処理が実行されないことを確認

よくあるエラーと対処法

⚠️ エラー対処チートシート

LINE署名検証失敗(Invalid LINE signature)

原因:チャネルシークレットの設定ミス、またはリクエストボディのパース方法の問題。n8nのWebhookノードで「Raw Body」オプションを有効にしているか確認してください。署名検証にはraw(生の)リクエストボディが必要です。JSONとしてパースされた後のボディを使うと署名が一致しません。

Supabase Webhookが未発火

原因:Webhookの設定でURLが正しくない、またはn8nのWebhookノードがアクティブになっていない。n8nのワークフローは「Active」にしないとWebhookが有効になりません。ワークフロー画面右上のトグルスイッチをONにしてください。また、Supabaseの「Database」→「Webhooks」画面でWebhookのステータスと最終実行ログを確認しましょう。

n8nのタイムアウト

原因:後続処理(AI生成・外部API呼び出しなど)に時間がかかりすぎている。LINEのWebhookは5秒以内にレスポンスを返す必要があります。時間のかかる処理はLINE返信受信ワークフローとは別の非同期ワークフローに分離し、Supabaseのステータス更新をトリガーとして起動する設計にしましょう。これが今回のアーキテクチャが採用している理由でもあります。

requestIdがundefinedになる

原因:LINEから送信されたテキストの形式が APPROVE:uuid になっていない。Flexメッセージのボタン設定でtextに APPROVE:{{ $json.id }}

この記事はハナ編集部が執筆しました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

はじめまして、「白米元気」と申します。

ノースキルで副業をスタートし、2ヶ月で月10万円を達成。
その後も毎日ChatGPTとにらめっこしながら、
「どうやったら仕組みで稼げるのか?」を考え続けてきました。

そんな中出会ったのが「LLM無職」です。
AIと仕組みを作り、AIに仕事をさせる。
副業や働き方そのものを実験していく——そんな挑戦をしています。

このブログでは、わたしのLLM無職への道のりの途中で
AIを活用した具体的な方法や工夫、日々の実践内容を紹介。
ときどき家族の話もまじえながら、
読んでくれた方が「なんかおもしろそう!」と思えるような、
リアルで実験的な情報をお届けしていきます。

コメント

コメントする

目次