Webhook
通过 POST /v1/webhooks 注册 URL,PUT /v1/webhooks/{id} 更新。创建时 CHECK_HEALTH 需返回 2xx。事件使用独立于 API 密钥的专用密钥签名。
REST 才是最终事实来源。即使漏收事件,也可通过 GET /v1/orders?updated_since=… 恢复状态,请设计定期对账。
webhook-events.zh-CN.json事件目录
| event_type | 触发时机 | 关键数据字段 |
|---|---|---|
order.completed | 订单已确认,eSIM 已就绪 | order_idexternal_refitems[] (order_item_id, iccid) |
order.failed | 订单未完成(交付失败等) | order_idexternal_refreason |
esim.delivered | 已发放 eSIM 并准备好安装信息 使用 `order_id` 和 `external_ref` 对应订单。`lpa` 仅在 POST 前解密,不在投递记录中明文保存。载荷不含 QR 图片 URL,请从 `lpa` 生成,或通过 `GET /v1/esims/{id}` 获取 `qr_code_base64`。 | order_item_idorder_idexternal_reficcidlpa |
esim.activated | eSIM 已安装并激活(smdp enabled/in_use) 使用 `order_id` 和下单时发送的 `external_ref` 将 eSIM 对应到您的订单。 | order_item_idorder_idexternal_reficcidactivated_at |
esim.usage_threshold | 使用量达到 75%/90% 阈值 用 `order_id` 和 `external_ref` 对应订单。每次只发送新跨越的最高阈值(0→95% 只发送 90)。`threshold` 表示至少已超过此阈值。 | order_item_idorder_idexternal_refthreshold (75/90)used_bytesdata_bytes |
esim.expiring | 距离到期 3 天/1 天 使用 `order_id` 和下单时发送的 `external_ref` 将 eSIM 对应到您的订单。 | order_item_idorder_idexternal_refdays_remaining (3/1)expires_at |
wallet.low_balance | 钱包达到 25%/10% 警告阈值 `threshold` 为百分比(25/10),仅在有真实百分比时附带,否则省略(请自行比较 `balance`)。绝不以 `threshold` 发送原始 VND 金额。 | threshold (25/10)balancecurrency |
catalog.updated | 套餐目录变化:新增、修改、隐藏、基础价格变化或售罄 不包含价格,因为各组织价格不同;请调用 `GET /v1/catalog/packages` 获取自身价格。短窗口内(默认 10 分钟)的变化合并为一个 `changes[]` Webhook,同套餐多次变化仅保留一项。同步数百套餐不会触发数百 Webhook。适用于测试和真实环境。 | changes[] (package_code, change: price|availability|package, occurred_at)change_count |
event_type: "ping"每次 `POST /v1/webhooks` 和 `PUT /v1/webhooks/{id}` 均在保存前进行健康检查.
不能通过 `events` 订阅或取消(填写 `ping` 返回 `internal.validation_failed`),所有端点均接收。使用与真实事件相同的信封及签名(`X-TKE-Webhook-Signature`,不带 `-Old`),可测试验签函数。处理器必须对未知 `event_type` 返回 2xx;返回 4xx 会导致创建或修改失败,报 `webhook.health_check_failed`(422)且不保存。`ping` 不进入 `GET /v1/webhooks/deliveries`。
请处理此事件类型,避免注册失败。
{
"event_id": "018f3c00-0000-7000-8000-0000000000c1",
"event_type": "ping",
"occurred_at": "2026-07-11T07:29:00Z",
"is_testmode": true,
"data": {
"message": "TongKho eSIM webhook health check"
}
}事件封装与签名请求头
所有 Webhook 使用相同信封。以 `HMAC-SHA256(webhook_secret, timestamp\nraw_body)` 签名,放入 `X-TKE-Webhook-Signature`(小写 hex)。24 小时轮换窗口内另附 `X-TKE-Webhook-Signature-Old`。至少投递一次,合作伙伴须按 `event_id` 去重。
{
"event_id": "018f3c00-0000-7000-8000-0000000000b0",
"event_type": "order.completed",
"occurred_at": "2026-07-11T07:30:00Z",
"is_testmode": false,
"data": {
"order_id": "018f2b00-0000-7000-8000-00000000009a",
"external_ref": "partner-cart-9931",
"items": [
{
"order_item_id": "018f2b00-0000-7000-8000-000000000001",
"iccid": "8984012345678901234"
}
]
}
}| 字段 | 说明 |
|---|---|
data | 按事件类型区分的载荷(见下文)。 |
event_id | 每个事件稳定且唯一的 UUID(用于去重)。 |
event_type | 事件类型。 |
is_testmode | 由测试密钥生成时为 true。 |
occurred_at | ISO-8601 UTC. |
目录同步: catalog.updated
套餐新增、修改、隐藏、基础价格或库存变化时触发,变更聚合于 changes[] 数组中,数百项变更仅产生 一次 对每个接收端的投递。
{
"event_id": "018f3c00-0000-7000-8000-0000000000c1",
"event_type": "catalog.updated",
"occurred_at": "2026-07-11T08:10:00Z",
"is_testmode": false,
"data": {
"changes": [
{
"package_code": "THAY-BANG-MA-GOI-CUA-BAN",
"change": "price",
"occurred_at": "2026-07-11T08:02:11Z"
},
{
"package_code": "VN_10GB_30D",
"change": "availability",
"occurred_at": "2026-07-11T08:04:52Z"
},
{
"package_code": "JP_3GB_7D",
"change": "package",
"occurred_at": "2026-07-11T08:09:30Z"
}
],
"change_count": 3
}
} 销售价格 按组织不同 ,发送统一价格既不准确也会泄露信息。此事件是 同步信号 :请调用 GET /v1/catalog/packages?market=… 刷新您的价格。
change 接受三个值: price (基础成本变化)、 availability (有货/缺货)、 package (新增/更新/隐藏)。遇到未知值也应触发同步。
签名、重试与去重依据 event_id 与标准事件相同,测试与正式环境按以下字段隔离 is_testmode.
验证 Webhook 签名
X-TKE-Signature 为使用 webhook_secret 与原始请求体字节计算的 HMAC-SHA256。
signing_string = timestamp + "\n" + raw_body # timestamp = header X-TKE-Webhook-Timestamp
signature = HMAC_SHA256(webhook_secret, signing_string) → hex lowercase
# 检查时间有效性(±300 秒),防止重放。
# 对收到的原始字节签名,不要重新序列化 JSON。重试退避策略
非 2xx 响应或 5 秒超时后,在 24 giờ 窗口内按指数间隔重试:1 phút → 5 phút → 30 phút → 2 giờ → 6 giờ。
Webhook 的 4 条关键建议
字段行为说明
esim.delivered.lpa 在 POST 前即时解密,不存明文。请用 LPA 生成二维码或调用 GET /v1/esims/{id}。
esim.usage_threshold: 仅触发新超出的最高阈值(0→95% 仅发送 threshold=90)。
wallet.low_balance.threshold 是百分比(25/10),并非 VND 金额。
catalog.updated 不含价格或详细库存,仅包含套餐代码及变更类型。