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を提供
承認フローの全体アーキテクチャ
全体の流れを整理すると、以下のようになります。
- リクエスト生成:n8nのワークフローがAI生成コンテンツなどを元に承認リクエストを作成
- DB保存:SupabaseのApproval_requestsテーブルにステータス「pending」でレコードを挿入
- LINE通知:n8nがLINE Push APIを呼び出し、承認ボタン付きFlexメッセージを送信
- 返信受信:ユーザーがLINEで「承認」または「却下」を返信→n8nのWebhookノードが受信
- ステータス更新:n8nがSupabaseのレコードをapproved/rejectedに更新
- 後続処理トリガー: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)にアクセスし、以下の手順でチャネルを作成します。
- 「プロバイダーを作成」→任意の名前を入力
- 「新しいチャネルを作成」→「Messaging API」を選択
- チャネル名・説明・業種を入力して作成
- 「チャネル基本設定」タブでチャネルシークレットをコピー(後でn8nの環境変数に設定)
- 「Messaging API設定」タブでチャネルアクセストークン(長期)を発行してコピー
- Webhook URLにn8nのWebhookノードのURLを入力し「検証」をクリック
- 「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通知を行います。
ノード構成:
- トリガーノード:Webhookノード(または任意のトリガー。AIノードの後段に接続)
- Supabaseノード(INSERT):approval_requestsテーブルにレコードを挿入。titleにコンテンツタイトル、contentに本文、line_user_idに通知先のLINEユーザーIDを設定
- 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のステータスを更新する別ワークフローを作成します。
ノード構成:
- Webhookノード:LINE Messaging APIのWebhook URLとして設定したエンドポイント。HTTP Methodは「POST」
- Codeノード(署名検証+メッセージ抽出):以下のコードを使用
- IFノード(スキップ判定):
{{ $json.skip }}がtrueの場合は処理を終了 - IFノード(承認/却下分岐):actionがAPPROVEかREJECTかで分岐
- Supabaseノード(UPDATE):各分岐でステータスを更新
- 後続処理ノード:承認時のみ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ノードを接続し、statusをapprovedに更新します。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:動作確認・エラー対処と応用パターン
テスト手順:実際に動かして確認する
実装が完了したら、以下の手順でエンドツーエンドのテストを行いましょう。
- 承認リクエスト生成ワークフローを手動実行:n8nのワークフロー画面で「Execute Workflow」をクリック。Supabaseにpendingのレコードが挿入され、LINEにFlexメッセージが届くことを確認
- LINEで「APPROVE:{id}」を送信:Flexメッセージの承認ボタンをタップ(またはテキストで直接送信)。n8nのLINE返信受信ワークフローの実行ログを確認
- Supabaseでレコードを確認:Table Editorでapproval_requestsテーブルを開き、該当レコードのstatusが「approved」に変わっていることを確認
- 後続処理の実行を確認:Supabase Webhookが発火してn8nの後続フローが動いているか、実行ログで確認
- 却下テスト:同様に「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 }}

コメント