← 所有文章
教程

UnifyPort 入站集成:先建 Webhook 的检查清单

做跨境客服或东南亚运营的消息集成时,第一步应该是先建 webhook,而不是先接账号。UnifyPort 没有通用的 REST 消息历史读取 API,也不保证错过的 payload 一定可重放;因此 webhook 就是你的入站记录层。先注册 POST /v1/webhook-endpoints,开启 signing_secret,订阅 message.received[*],把事件入库,再交给客服系统、CRM、AI 或自动化流程。

关键结论

  • 先注册 webhook,再连接生产消息账号。
  • 设置 signing_secret,让投递带上 X-Device-TimestampX-Device-Signature
  • 慢任务开始前,先保存标准事件 envelope。
  • 只做入站 inbox 时,订阅 message.received,并检查 data.message.direction === "inbound"
  • 事件过滤、签名验证、重试处理和业务路由要分层实现。

为什么 webhook 要放在最前面

客户消息可能在 CRM、AI agent 或共享 inbox 准备好之前就已经到达。如果接收端还没注册,就不能假设之后一定能通过消息历史 API 把它补回来。UnifyPort 的 Quickstart 也把 webhook 注册放在账号授权之前。

这篇清单可以和两篇已有教程一起看:Webhook HMAC replay protection 讲时间戳、签名和幂等,UnifyPort webhook event filterssubscribed_events 和通配符该怎么选。这里把它们串成一个落地顺序。

步骤 1:创建一个 signed endpoint

真实 API 路由是 POST /v1/webhook-endpoints。入站 inbox 可以只订阅 message.received;完整事件收集器可以用 [*]

curl -X POST https://api.unifyport.ai/v1/webhook-endpoints \
  -H "X-Api-Key: $UNIFYPORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"url\": \"$PUBLIC_WEBHOOK_URL\",
    \"status\": \"active\",
    \"subscribed_events\": [\"message.received\"],
    \"signing_secret\": \"$WEBHOOK_SIGNING_SECRET\",
    \"retry_policy\": { \"max_attempts\": 3 }
  }"

retry_policy.max_attempts 表示初次投递之后还能重试几次。文档中的默认值是 3,允许范围是 05。接入时请以 Create webhook endpoint 为准。

步骤 2:验证原始请求体

开启签名后,UnifyPort 会发送 X-Device-Signature。它是下面这个字符串的 HMAC-SHA256 十六进制摘要:

<X-Device-Timestamp>.<raw request body>

接收端必须在 JSON 解析或重新序列化之前验证原始 bytes。Node.js 的 crypto.createHmac()crypto.timingSafeEqual() 适合这个模式;官方文档也要求 timingSafeEqual() 比较的 buffer 长度一致。

import crypto from 'node:crypto';
import express from 'express';

const app = express();
const signingSecret = process.env.WEBHOOK_SIGNING_SECRET;

app.post('/webhooks/unifyport', express.raw({ type: 'application/json' }), async (req, res) => {
  const timestamp = req.get('X-Device-Timestamp') ?? '';
  const signature = req.get('X-Device-Signature') ?? '';

  const expected = crypto
    .createHmac('sha256', signingSecret)
    .update(timestamp + '.')
    .update(req.body)
    .digest('hex');

  const valid =
    /^[0-9a-f]{64}$/i.test(signature) &&
    signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));

  if (!valid) return res.sendStatus(401);

  const event = JSON.parse(req.body.toString('utf8'));
  await inbox.insertIfAbsent(event.id, event);
  return res.sendStatus(202);
});

生产环境还要补上时间戳新鲜度、持久化幂等和重试确认策略,细节见 Webhook delivery and signature verification

步骤 3:先保存标准事件 envelope

message.received 在不同 provider 上保持相同顶层结构:

{
  "id": "evt_2f9c1a4b7e",
  "type": "message.received",
  "provider": "whatsapp",
  "account_id": "acc_8c21d0",
  "occurred_at": "2026-06-08T12:34:56Z",
  "data": {
    "conversation": { "id": "8613912345678", "type": "user", "title": "Jordan Lee" },
    "sender": { "id": "8613912345678", "name": "Jordan Lee", "type": "user" },
    "message": {
      "id": "wamid.HBgM",
      "text": "Hi - is my order shipped yet?",
      "direction": "inbound",
      "sent_at": "2026-06-08T12:34:55Z"
    }
  }
}

至少保存 idtypeprovideraccount_idoccurred_atdata.conversation.iddata.sender.iddata.message.id。如果后面接 n8n,可以参考 n8n WhatsApp AI agent tutorial:n8n 适合处理可信事件,不适合承担第一道安全边界。

限制与取舍

非官方接口适合需要从普通或既有消息账号接收入站消息的团队;如果你需要平台认证、官方商业功能或 provider 级政策保证,应选择对应官方 API。另一个常见误区是把 message.received 当成只代表入站:它可能包含入站或出站,inbox 流程必须检查 data.message.direction

FAQ

应该先订阅哪个事件?

只做入站 inbox,就先订阅 message.received。只有当端点是通用事件收集器时,才使用 [*]

签名验证就足够防重放吗?

不够。HMAC 验证完整性和共享密钥,仍然需要时间戳新鲜度和按事件 ID 的持久化去重。

CRM 写入完成前可以返回 200 吗?

可以,但前提是事件已经进入你的持久化 inbox 或队列。CRM、AI 和通知可以异步处理。

以后还能加 LINE、Zalo 或 X 吗?

可以。统一 envelope 里有 provideraccount_id,下游按平台分支即可,入口仍然是同一个 signed webhook。

下一步

打开 Create webhook endpoint,先注册 receiver,再按 webhook delivery guide 完成验证和确认策略。

Sources

官方来源核对日期:2026-08-26。

UnifyPort API

让消息接入变成一条稳定的产品管线。

先用统一 API 跑通发送,再用标准事件把所有入站消息接回业务系统。