ベトナムのサポートチームが Zalo、WhatsApp、LINE をひとつの受信キューにまとめた方法
月曜の朝 8:12、ホーチミン市のサポート責任者がまだノート PC を開く前に、最初の Zalo メッセージが届いた。顧客は配送会社の集荷前に住所を変更したいという。4 分後、バンコクの仕入先が LINE で「1 箱遅れる」と連絡してきた。8:27 には、シンガポールのリピーターが WhatsApp で保証条件を質問した。
どれも珍しいメッセージではない。問題は、それらが 3 つの入口、3 台のスマートフォン、3 人の担当者に分散して届いていたことだった。9 時までに、チームは 2 枚のスクリーンショットを Slack に貼り、1 件を営業に転送し、どの顧客が保証について質問したのかを忘れていた。
この会社は、小さなビューティーブランドの越境注文を支える 5 人のオペレーション兼サポートチームだ。顧客は主にベトナム、タイ、シンガポールにいる。ローカル購入者は Zalo、タイのパートナーは LINE、仕入先と海外のリピーターは WhatsApp を使う。チームが必要としていたのはキャンペーン配信でも、テンプレート送信でも、新しい CRM でもない。必要だったのは、すべての受信メッセージを作業項目として見えるようにすることだった。
3つの受信箱、1つの SLA
変更前の運用は手作業だが、よくある形だった。Zalo はベトナム担当のスマートフォンにある。LINE はタイの仕入先を担当するオペレーションメンバーが持つ。WhatsApp は創業者のスマートフォンに残っている。古い顧客がまだ創業者の番号を使っていたからだ。毎朝、全員が Slack を開き、急ぎに見えるメッセージをスクリーンショットで共有した。
量が少ないうちは、これでも回った。しかし、住所変更の見落としが返金につながり、仕入先の LINE 連絡に気づく前に倉庫が当日出荷を約束し、創業者の出張中に WhatsApp の保証質問が放置されるようになった。
2 週間で、チームは 31 件の遅延返信を記録した。ほとんどは技術障害ではない。可視性の問題だった。誰かは見ているが、チーム全体には見えていない。あるいは、担当者が不在で、他の人はそのメッセージの存在すら知らない。
各公式プラットフォームのルートは、より大きな問題を解くものだった。Zalo Official Account は公式アカウント運用や通知テンプレートに向いている。LINE Official Account は rich menu、配信、ブランド化された顧客接点に向いている。WhatsApp Cloud API は大規模なテンプレート型ビジネスメッセージに向いている。しかし、このチームは大規模に会話を開始したいわけではなかった。重要な会話はすべて、顧客か仕入先から始まっていた。
つまり、プロジェクトの要件は変わった。「3つの公式ビジネスメッセージ基盤を導入する」ではなく、「すべての受信メッセージを信頼できるキュー項目にする」だった。
本当に必要だった webhook
チームはバックエンドに 1 つの endpoint を作った。
POST /inbound/messages
その後、UnifyPort を通じて Zalo アカウント、WhatsApp アカウント、LINE アカウントを 1 つずつ接続した。3 つのアカウントはすべて同じ webhook endpoint に配信される。すべての配信は、id、type、provider、account_id、occurred_at、data という同じ envelope を持つ。
正規化後の Zalo メッセージは次の形になる。
{
"id": "evt_7f4c20a91d",
"type": "message.received",
"provider": "zalo",
"account_id": "acc_vn_support",
"occurred_at": "2026-07-02T01:12:16Z",
"data": {
"conversation": { "id": "zalo_user_1942", "type": "user", "title": "Minh Anh" },
"sender": { "id": "zalo_user_1942", "type": "user", "name": "Minh Anh" },
"message": {
"id": "zalo_msg_8831",
"type": "text",
"text": "Em muốn đổi địa chỉ giao hàng trước 11h được không?",
"direction": "inbound",
"sent_at": "2026-07-02T01:12:15Z"
},
"event": { "kind": "message_received" }
}
}
同じ handler が WhatsApp と LINE も処理する。変わるのは provider だけで、プラットフォームごとに別の parser は要らない。
payload を信頼する前に、endpoint は配信署名を検証する。endpoint に signing_secret を設定しているため、各 request には X-Device-Timestamp と X-Device-Signature が含まれる。署名は timestamp、ドット、raw request body を連結した値の HMAC-SHA256 hex digest だ。
import crypto from 'crypto';
import express from 'express';
const app = express();
const secret = process.env.WEBHOOK_SIGNING_SECRET;
app.post('/inbound/messages', 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', secret);
hmac.update(timestamp + '.');
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'));
await enqueueInboundMessage(event);
res.status(200).end();
});
キュー項目として保存したのは、チームが実際に見る 5 つの値だけだった。プラットフォーム、顧客名、本文、conversation ID、account ID。その他の値は監査用の raw event metadata として残した。
説明できるルーティングルール
最初の routing は、あえて単純にした。
Zalo はベトナムサポート lane に入る。LINE は仕入先 lane に入る。WhatsApp は海外顧客 lane に入る。注文番号を含むメッセージは CRM note を作る。配送関連のキーワードを含むメッセージは Slack の #fulfillment に投稿する。
最初の週に AI classifier は使わなかった。注文が動いている最中でも、チームが自分たちで調査できる仕組みが必要だったからだ。ルール名は delivery_change、supplier_delay、warranty_question のようにし、各 item にはどのルールが一致したかを表示した。
2 週目に追加したのは、3 種類の定型質問に対する draft suggestion だけだった。住所変更なら新住所と注文番号を確認する文面、保証質問なら保証期間と写真要件、仕入先遅延なら新しい引き渡し時間を確認する文面を作る。draft は Slack に投稿されるだけで、自動送信はしない。
この境界は重要だった。チームは自動化に顧客対応を任せたいのではない。次の人間のアクションを明確にしたかった。
4週間後に変わったこと
目に見える変化は単純だった。誰も 3 台のスマートフォンを監視しなくなった。サポート責任者は朝、1 つのキューを開くだけで Zalo、WhatsApp、LINE を同じ一覧で見られる。provider で絞り込むことはできるが、標準ビューは時系列の inbound work だ。
初回返信の中央値は 2 時間 18 分から 26 分に下がった。4 時間以上放置されたメッセージは、2 週間で 31 件から次の 2 週間で 3 件になった。さらに重要なのは、スクリーンショットが運用記録ではなくなったことだ。各 inbound event には安定した event ID、provider、account ID、timestamp がある。
CRM も使えるものになった。以前は、顧客の Zalo 履歴は 1 台のスマートフォンにあり、仕入先の LINE 文脈は 1 人の担当者だけが持っていた。変更後は、CRM に各チャネルの直近メッセージが残る。創業者が更新商談の準備をするとき、チームにチャット検索を頼む必要はない。contact record を開くだけでよい。
顧客は新しい入口に移されたわけではない。これまで通り、同じ Zalo、WhatsApp、LINE アカウントにメッセージを送る。変わったのは、メッセージが届いた後の backend path だけだった。
このパターン
小さなチームにとって重要なのは、inbound support と outbound campaign infrastructure を分けて考えることだ。公式の business product は、認証済み表示、broadcast、template、rich menu、campaign operations が必要なときに価値がある。しかし、いまの痛みが「顧客が先にメッセージを送ってくるのに、チームが見落とす」なら、最初に必要なのは inbound routing layer だ。
UnifyPort を使うことで、このチームは各プラットフォームを 1 つの event stream として扱った。message.received が共通 contract になり、HMAC-SHA256 検証が配信を信頼できるものにし、provider フィールドがどこから来たメッセージかを示す。残りのサポート workflow は platform-neutral のままだ。
5 人のチームには、それで十分だった。1 つの webhook、1 つのキュー、そして既に使っていたツールだけでよかった。