一个越南客服团队如何把 Zalo、WhatsApp 和 LINE 放进同一个入站队列
周一早上 8:12,胡志明市的客服负责人还没打开电脑,第一条 Zalo 消息已经进来了。客户想在快递揽收前修改收货地址。4 分钟后,曼谷供应商通过 LINE 说有一箱货会延迟。8:27,一个新加坡老客户又在 WhatsApp 上询问保修条款。
这些消息本身都不特殊。真正的问题是,它们分别落在三个入口、三台手机、三个人手里。到 9 点,团队已经把两张截图转到 Slack,把一条消息转给销售,又忘了到底是哪位客户问了保修问题。
这是一家小型美妆品牌的五人运营客服团队,负责跨境订单支持。客户主要来自越南、泰国和新加坡,所以渠道组合很典型:本地买家用 Zalo,泰国合作方用 LINE,供应商和国际复购客户用 WhatsApp。团队不需要营销活动,不需要群发模板,也不想换一套新的 CRM。它需要的只是把入站消息变成可处理的工作项。
三个收件箱,一个响应要求
改造前,他们的流程很手工,但也很常见。Zalo 在越南客服负责人的手机上,LINE 在负责泰国供应商的运营同事手机上,WhatsApp 在创始人手机上,因为很多老客户还存着他的号码。每天早上,大家打开 Slack,把看起来紧急的消息截图贴进去。
这个流程在消息量不高时勉强可用。后来问题开始变得具体:快递揽收前没看到改地址消息,最后只能退款;供应商在 LINE 上提醒延迟,被看到时仓库已经承诺当天发货;创始人在出差,WhatsApp 里的保修问题就一直没人处理。
两周内,团队记录了 31 次超时回复。多数不是技术故障,而是可见性故障。有人看到了消息,但团队其他人没有。或者应该处理的人不在线,其他人甚至不知道这条消息存在。
官方路径解决的是更大的问题。Zalo Official Account 适合官方账号运营和通知模板;LINE Official Account 适合 rich menu、群发和品牌化客户入口;WhatsApp Cloud API 适合规模化模板消息。但这个团队并不主动发起大规模对话。关键对话都是客户或供应商先发来的。
所以项目目标变了。它不是“接入三套官方商业消息系统”,而是“把每条入站消息变成一个可信的队列任务”。
他们真正需要的 webhook
团队在后端创建了一个入口:
POST /inbound/messages
然后通过 UnifyPort 接入一个 Zalo 账号、一个 WhatsApp 账号和一个 LINE 账号。三个账号都投递到同一个 webhook endpoint。每次投递都使用同一套 envelope:id、type、provider、account_id、occurred_at 和 data。
归一化后的 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,不是解析逻辑。
在信任 payload 之前,接口会校验投递签名。endpoint 设置了 signing_secret,所以每个请求都会带 X-Device-Timestamp 和 X-Device-Signature。签名内容是 timestamp、一个点和原始请求 body 的 HMAC-SHA256 十六进制摘要。
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();
});
队列任务只抽取团队最关心的五个字段:平台、客户名、消息正文、conversation ID 和 account ID。其余内容作为原始事件元数据保留,方便后续审计。
团队能解释清楚的路由规则
第一版路由故意做得很简单。
Zalo 消息进越南客服队列。LINE 消息进供应商队列。WhatsApp 消息进国际客户队列。任何带订单号的消息都会创建一条 CRM 记录。任何包含配送关键词的消息都会发到 Slack 的 #fulfillment 频道。
第一周没有 AI 分类器。团队需要的是在订单流转过程中也能排查的系统。他们使用 delivery_change、supplier_delay、warranty_question 这样的规则名,每个任务都会显示命中的规则。
第二周,他们只加了一层辅助能力:给三类高频问题生成回复草稿。修改地址会生成一条请求新地址和订单号的回复;保修问题会生成包含保修窗口和照片要求的模板;供应商延迟消息会生成询问新交接时间的回复。草稿只发到 Slack,不会自动发给客户。
这个边界很重要。团队不想让自动化直接和客户对话,只想让下一步人工动作更明确。
四周后发生了什么
最明显的变化很简单:没人再盯三台手机。客服负责人每天早上只打开一个队列,就能看到 Zalo、WhatsApp 和 LINE。团队仍然可以按 provider 筛选,但默认视图是按时间排序的入站任务。
首次响应中位数从 2 小时 18 分钟降到 26 分钟。超过 4 小时未处理的消息从两周 31 条降到接下来两周 3 条。更重要的是,团队不再把截图当作运营记录。每条入站事件都有稳定 event ID、provider、account ID 和时间戳。
CRM 也变得更有用。以前客户的 Zalo 历史在一台手机上,供应商的 LINE 上下文在某个运营同事那里。改造后,CRM 能看到各渠道最近一条消息。创始人准备续约沟通时,不需要再让团队翻聊天记录,直接打开联系人记录即可。
客户没有被迁移到新入口。客户仍然发消息到原来的 Zalo、WhatsApp 或 LINE 账号。变化只发生在消息到达后的后端路径。
这个模式适合谁
对小团队来说,关键是把“入站客服”和“出站营销基础设施”分开看。官方商业产品在需要认证展示、群发、模板、rich menu 或活动运营时很有价值。但如果当前痛点是“客户先发消息,我们团队漏看”,最先需要的是入站路由层。
通过 UnifyPort,这个团队把各平台当作同一条事件流处理。message.received 成为共同契约,HMAC-SHA256 校验让投递可信,provider 字段告诉队列消息来自哪里,其余客服流程保持平台无关。
对一个五人团队来说,这就够了。一个 webhook,一个队列,再加上他们原本就在用的工具。