署名付きインバウンド Webhook から n8n WhatsApp AI Agent を作る
2026 年に WhatsApp AI Agent を素早く作るなら、最初に Agent を作り込むより、インバウンドの入口を固める方が実用的です。
公開コミュニティの課題ははっきりしています。最近の n8n Reddit スレッドでは、ある開発者が WhatsApp automation を DeepSeek とつなぎ、メディア処理と本番安定性を求めつつ、Meta Business verification と Cloud API セットアップで足止めされたくないと相談していました。別の議論では、WhatsApp trigger が一度しか動かない、test mode でしか動かない、webhook delivery と workflow response のタイミングが合わない、といった問題が繰り返し出ています。
小さなチームが最初に直すべきなのは順番です。メッセージを確実に受け取り、すばやく確認応答し、保存してから、AI Agent に返信するかどうかを判断させます。
UnifyPort はインバウンド部分として署名付き message.received イベントを提供します。n8n はワークフローのキャンバスを提供します。間に小さなエッジ検証サービスを置くと、本番向きの経路になります。
WhatsApp customer message
-> UnifyPort signed message.received webhook
-> Edge verifier for X-Device-Signature
-> n8n production Webhook URL
-> AI triage, CRM lookup, Slack alert, or reply via POST /v1/messages
日本やタイでは LINE が主要チャネルになることも多いですが、入口の考え方は同じです。WhatsApp で始めても、後で LINE を追加しても、最初の契約は「署名された入站イベントを保存してから処理する」です。
n8n webhook URL が重要な理由
n8n の公式 Webhook node documentation は、Webhook node には test URL と production URL の 2 種類があると説明しています。test URL はエディタが listen している間の手動テスト用です。workflow を active にした後に外部システムへ設定するのは production URL です。
顧客メッセージのパイプラインは production URL を前提に設計します。エディタが listen していなかったからといって、顧客が同じメッセージを送り直してくれるわけではありません。インバウンド経路は常に active で、安定していて、すばやく 2xx を返せる必要があります。
n8n の Respond to Webhook node は、workflow が HTTP response を制御したいときに便利です。インバウンドメッセージでは response を単純に保ちます。イベントを受け取り、キューまたはストレージに渡し、200 を返します。長い AI 推論、CRM 書き込み、外向き返信は delivery を acknowledge した後で実行します。
UnifyPort webhook を登録する
UnifyPort で webhook endpoint を作成し、message.received を購読します。signing_secret を設定すると、各 delivery に X-Device-Timestamp と X-Device-Signature が付きます。
curl -X POST https://api.unifyport.ai/v1/webhook-endpoints \
-H "X-Api-Key: <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://edge.example.com/unifyport/n8n",
"status": "active",
"subscribed_events": ["message.received"],
"signing_secret": "<WEBHOOK_SIGNING_SECRET>"
}'
ここでは n8n URL を直接入れません。小さなエッジ検証サービスの URL を入れます。UnifyPort は HMAC-SHA256 で raw request body に署名するため、署名検証を厳密に行うには raw bytes が必要です。署名対象の文字列は次の形です。
<X-Device-Timestamp>.<raw request body>
n8n のデプロイで JSON parse 前の完全な raw bytes を扱えるなら、n8n 内で検証しても構いません。多くのチームでは、小さなサービスで検証し、内部 token を付けて信頼済みイベントだけを n8n production webhook に転送する方が境界を保ちやすくなります。
エッジ検証サービスを追加する
次は Node.js の完全な検証サービスです。UnifyPort 署名を検証し、イベントを parse し、イベント種別を確認して、信頼済み payload を n8n に転送します。
import crypto from "crypto";
import express from "express";
const app = express();
const signingSecret = process.env.WEBHOOK_SIGNING_SECRET;
const n8nWebhookUrl = process.env.N8N_PRODUCTION_WEBHOOK_URL;
const internalToken = process.env.N8N_INTERNAL_TOKEN;
app.post("/unifyport/n8n", express.raw({ type: "application/json" }), async (req, res) => {
const timestamp = req.get("X-Device-Timestamp") || "";
const signature = req.get("X-Device-Signature") || "";
const hmac = crypto.createHmac("sha256", signingSecret);
hmac.update(timestamp);
hmac.update(".");
hmac.update(req.body);
const expected = hmac.digest("hex");
const valid =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!valid) {
res.status(401).end();
return;
}
const event = JSON.parse(req.body.toString("utf8"));
if (event.type !== "message.received") {
res.status(202).end();
return;
}
await fetch(n8nWebhookUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Internal-Token": internalToken
},
body: JSON.stringify(event)
});
res.status(200).end();
});
app.listen(3000);
このサービスは WhatsApp アカウントの資格情報を保存せず、Agent の返信内容も決めません。役割は、イベントが自分の UnifyPort endpoint から届いたことを証明し、信頼できるイベントを n8n に渡すことだけです。
n8n workflow を作る
n8n で active な workflow を作成し、Webhook trigger の production URL でイベントを受け取ります。入力 JSON はすでに UnifyPort の標準イベントです。
{
"id": "evt_2f9c1a4b7e",
"type": "message.received",
"provider": "whatsapp",
"account_id": "acc_8c21d0",
"occurred_at": "2026-07-08T02:30:00Z",
"data": {
"conversation": { "id": "8613912345678", "type": "user", "title": "Jordan Lee" },
"sender": { "id": "8613912345678", "name": "Jordan Lee", "type": "user" },
"message": {
"id": "wamid.HBgM",
"type": "text",
"text": "Can I change the delivery address?",
"direction": "inbound",
"sent_at": "2026-07-08T02:29:59Z"
},
"event": { "kind": "message_received" }
}
}
最初の実用的な workflow は 5 ノードで十分です。
- Webhook: 転送されたイベントを受け取る。
- IF:
typeがmessage.received、providerがwhatsappであることを確認する。 - Data store、Postgres、Airtable、CRM:
id、account_id、provider、data.conversation.id、data.sender.id、data.message.textを保存する。 - AI Agent または model gateway への HTTP Request: sales、support、billing、人間への引き継ぎに分類する。
- HTTP Request: 必要な場合に UnifyPort から返信する。
先に保存してください。UnifyPort の webhook documentation では、イベントがインバウンド traffic の唯一の記録として扱われます。missed payload を後から読む message-history API はないため、遅い AI ステップが失敗する前にイベントを永続化します。
workflow が決めてから返信する
Agent が返信すべき場合は POST /v1/messages を使います。受信者は inbound event から取り、返信は明示的な action として扱います。
curl -X POST https://api.unifyport.ai/v1/messages \
-H "X-Api-Key: <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"account_id": "acc_8c21d0",
"to": { "id": "8613912345678", "type": "user" },
"message": {
"type": "text",
"text": "Yes. Send us the new delivery address and we will update the order note."
}
}'
n8n では HTTP Request node です。account_id は {{$json.account_id}}、recipient id は {{$json.data.sender.id}}、message.text は AI node の出力から map します。
Agent を最初の入口にしない理由
AI Agent は顧客メッセージに最初に触れるシステムにするべきではありません。最初のシステムは退屈なくらい単純であるべきです。検証、acknowledge、保存、route。そうすれば model、prompt、escalation policy が変わっても、運用上の契約は安定します。
この構造は WhatsApp 以外にも広げられます。標準 envelope は provider、account_id、occurred_at、data を使います。あとで Telegram、LINE、Zalo、TikTok、X を接続しても、workflow は provider で分岐できます。6 種類の webhook 形式を覚える必要はありません。
公式 WhatsApp Business Developer Hub は、Meta Cloud API、webhooks、pricing、policy surface を学ぶ場所として今も重要です。公式 platform 上に構築するなら参照してください。一方で、WhatsApp inbound support agent を n8n に接続し、すべての公式セットアップ手順を workflow に持ち込みたくない場合、UnifyPort の unofficial interface は仕事を狭く定義します。署名された顧客メッセージを受け取り、チームがすでに使っているツールへ渡すことです。
まずエッジを安定させる。エッジが信頼できれば、Agent はただの次のノードになります。