Z Zise Developers

kyc.result.updated

实名审核有结果,或被要求补充材料

何时发

两件事共用这一条:审核出结论(status: updated)、被要求补件 (status: supplement_required)。 ⚠ 通过与驳回是同一个 status: updated —— 事件体里没有任何字段能区分, 这是「只带 ID 与状态」那条红线的直接后果。收到它必须回查 GET /v1/kyc(带 x-on-behalf-of)才知道结论;照 status 直接放行是错的。 ⚠ data.id会员内部 id,不是 KYC 单号 —— 这是刻意的,不是缺陷: 开放 API 上的实名回查是 GET /v1/kyc + x-on-behalf-of,按人查、 根本没有「KYC 单号」这个入参(而且 L1 / L2 分表,同一个人不止一行)。 L1 与 L2 也不分事件,回查时看返回里的层级。 ⚠ status_version 恒为 0,而这一条会来很多次(L1 通过、L2 补件、 L2 通过、驳回后重交……)。请按 created_at 排序,并且每一条都回查一次 —— 事件本身不带结论。

这条事件不由任何 API 调用发起

审核结论由我方风控/人工作出,没有任何商户端点能发起它。(POST /v1/kyc/sessions 待建,建成后这一行要撤掉并在下单端点上引用。)

事件体

{
  "event_id": "evt_1a55e9c73b0d47f2ab6c8d19e4f05237",
  "event_type": "kyc.result.updated",
  "created_at": "2026-08-12T11:15:40Z",
  "merchant_id": "acme",
  "livemode": true,
  "data": {
    "object": "kyc",
    "id": "4b7c1e02-9a3d-4f18-8c55-2d61ab0f9e77",
    "external_member_id": "u_88123",
    "status": "updated",
    "status_version": 0
  }
}

事件体字段

字段类型说明
idstring去重用这个evt_…,同一事件重投时不变。
typestring固定为 kyc.result.updated
created_atstring事件产生时刻(RFC3339)。不是投递时刻 —— 重投时它不变。
data.objectstring对象类型,决定 data.id 该拿去查哪个端点
data.idstring对象 ID,拿它回查详情
data.statusstring认不出的值按未知处理并告警,不要 fallback 成「处理中」
data.status_versionnumber单调递增,用它把慢到的旧状态丢掉

验签与去重

验签用原始请求体字节,不要先解析再重新序列化 —— 你的 JSON 库与我方的键顺序、空格几乎一定不同,重新序列化出来的签名一定对不上, 而那个错误长得像「密钥配错了」。

// Node · 放在解析 JSON 之前
const raw = await readRawBody(req);            // Buffer / string,别用已解析的 req.body
const expect = crypto.createHmac("sha256", WEBHOOK_SECRET).update(raw).digest("hex");
const got = req.headers["z-signature"];        // 形如 t=<unix>,v1=<hex>
if (!timingSafeEqual(expect, parseV1(got))) return res.status(400).end();

// 去重:用信封的 id,不是 data.id
if (await seen(JSON.parse(raw).id)) return res.status(200).end();

完整做法(含时间戳容差与重投语义)见 Webhook 指南