← 所有文章
案例分析

一个越南客服团队如何把 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:idtypeprovideraccount_idoccurred_atdata

归一化后的 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-TimestampX-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_changesupplier_delaywarranty_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,一个队列,再加上他们原本就在用的工具。