§ 00Webhook 是什么
当 EasyLiveChat 这边发生事情 —— 客户在你的小部件中发消息、客服关闭对话、发生与审计相关的操作 —— 我们会向你注册的每个 URL 发出 HTTPS POST。请求体是 JSON,包含完整的事件负载,让你的服务器无需轮询即可响应。
可以把它看作 REST API 的镜像。API 让你控制的代码触达 EasyLiveChat。Webhook 让 EasyLiveChat 触达你控制的代码。大多数集成两者并用。
每个注册端点都有自己的密钥。我们用该密钥对每个请求体进行 HMAC-SHA256 签名,并在 x-easylivechat-signature 头里发送十六进制摘要。你的服务器用同一个密钥重新计算 HMAC 并比对 —— 匹配表示请求确实来自我们,不匹配则丢弃。
§ 01套餐可用性
Webhook 端点可在所有付费套餐和 14 天免费试用期内创建。按事件不收费 —— 多端点扇出已包含。
audit.event 流仅限 Enterprise,由 auditWebhookExport 权益控制。message.created 和 conversation.updated 在所有允许 API 访问的套餐上触发。
§ 02配置端点
在以下位置添加端点 https://app.livechattools.com/settings/webhooks. 我们会为每个端点生成一个随机密钥,仅在创建时显示一次。把它当作密码看待 —— 立刻复制到你的服务器密钥库。
- URL — 必须为 https:// 且解析到公网 IP。我们在创建和投递时都会拒绝回环、私有和云元数据地址。
- 事件 — 可以选择具体的事件名或订阅 *(所有事件)。通配符方便原型开发,但更紧凑的事件列表能让你的处理逻辑更容易推理。
- 密钥 — 自动生成,仅展示一次。用于签名每次投递。一旦丢失,请删除端点并新建一个 —— 无法找回。
默认启用。你可以随时在同一页面撤销端点 —— 投递立即停止。
§ 03负载结构
每次投递都是 content-type: application/json 的 POST 请求。请求头标识事件并携带签名;请求体信封始终是 { event, deliveredAt, data }:
POST https://your-server.example.com/webhook
content-type: application/json
user-agent: EasyLiveChat-Webhooks/1.0
x-easylivechat-event: message.created
x-easylivechat-signature: sha256=<hex>
{
"event": "message.created",
"deliveredAt": "2026-05-27T08:55:25.841Z",
"data": {
"conversationId": "cmp...",
"message": {
"id": "cmp...",
"body": "Hi!",
"senderType": "CUSTOMER",
"createdAt": "2026-05-27T08:55:25.840Z"
}
}
}event 与 x-easylivechat-event 头同值。data 的结构因事件而异 —— 见下方事件目录。
§ 04验证签名
任何知道你端点 URL 的人都可以向它发垃圾 POST。HMAC 签名就是用来区分我们的请求和伪造请求的。用你的密钥重新计算,并在不匹配时拒绝请求。
读取原始 body 字节(不要先 json.parse —— 重新序列化会重排键,导致哈希错误)。从签名头中去掉 sha256= 前缀。计算 HMAC-SHA256(rawBody, secret) 并与收到的十六进制进行时间安全比较。
Node.js
import crypto from 'node:crypto';
import express from 'express';
const SECRET = process.env.WEBHOOK_SECRET;
const app = express();
app.use(express.raw({ type: 'application/json' }));
app.post('/webhook', (req, res) => {
const received = (req.header('x-easylivechat-signature') || '').replace(/^sha256=/, '');
const expected = crypto.createHmac('sha256', SECRET).update(req.body).digest('hex');
const ok =
received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received, 'hex'), Buffer.from(expected, 'hex'));
if (!ok) return res.status(401).send('bad signature');
const event = JSON.parse(req.body.toString('utf8'));
// handle event.event, event.data...
res.status(200).send('ok');
});Python (Flask)
import hmac, hashlib, os
from flask import Flask, request, abort
SECRET = os.environ["WEBHOOK_SECRET"].encode()
app = Flask(__name__)
@app.post("/webhook")
def webhook():
received = request.headers.get("x-easylivechat-signature", "").removeprefix("sha256=")
expected = hmac.new(SECRET, request.get_data(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(received, expected):
abort(401)
event = request.get_json()
# handle event["event"], event["data"]...
return "ok", 200curl (debugging)
# Recompute the signature locally and compare with the header. echo -n "$RAW_BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET"
timingSafeEqual / hmac.compare_digest — 始终使用恒定时间比较,避免攻击者逐字节探测密钥。
§ 05事件目录
目前平台触发四种事件类型。新功能上线时会增加 —— 想要前向兼容请订阅 *。
| 事件 | 触发条件 | 数据结构 |
|---|---|---|
| message.created | 对话上有新消息到达,无论谁发送(客户入站、客服出站、REST API 发送)。 | { conversationId, message } |
| conversation.updated | 对话状态(OPEN · SNOOZED · CLOSED)或其 assignedAgentId 发生变化。 | { conversationId, status?, assignedAgentId? } |
| audit.event | 每写入一行审计日志时 —— 客服登录、角色变更、集成创建、密钥撤销等。(Enterprise 套餐) | { action, entity, entityId, actorId, metadata } |
| webhook.test | 你点击「设置 → Webhooks」中的「Test」。签名和结构与真实事件完全一致。 | { tenantId, test: true } |
订阅 * 接收所有当前与未来事件。如想限定处理器需要了解的范围,则订阅具体事件名。
§ 06测试集成
「设置 → Webhooks」中的「Test」按钮会向你的端点触发一个 webhook.test 事件,并报告上游 HTTP 状态码和耗时。用它来验证你的处理器,然后再接入真实流量。
测试负载使用与生产事件完全相同的请求体信封、请求头名称和 HMAC 方案 —— 如果你的验证对 webhook.test 通过,那么对 message.created 和其他事件也会通过。
§ 07重试与超时
我们给你的服务器 10 秒响应时间。任何非 2xx 状态或网络错误都算失败。失败的投递最多重试 5 次,采用从 2 秒起的指数退避,所以不稳定的端点在我们放弃该事件之前有多次机会。
我们不保证恰好一次投递 —— 网络抖动时同一个事件可能到达两次。让你的处理器幂等:用内部 data.message.id(或对该事件有意义的 id)去重。
§ 08安全清单
- 始终使用 https:// —— 创建时会拒绝纯 http:// 端点。
- 我们在创建和投递时都会拦截私有、回环和云元数据 IP,配置错误的 DNS 记录不能被用来转入你的网络。
- 用时间安全的相等比较签名(Node crypto.timingSafeEqual、Python hmac.compare_digest、Go hmac.Equal)。
- 通过删除端点并用同一 URL 创建新端点来轮换。原子性地更新你的密钥库。