Z Zise Developers

card.status.updated

卡片状态发生变化(冻结 / 解冻 / 激活 / 销卡 / 制卡与物流推进)

何时发

上游回报的卡片状态与我方记录不一致、我方把它同步过来那一刻。 一条事件覆盖全部状态变化,具体变成了什么事件体里没有 —— 拿 data.id(就是卡 id)回查 GET /v1/cards/crd_<id>status。 ⚠ 2026-08-13 之前这一条发的是会员内部 id,只能先列出这个人的全部卡 再逐张比对(而多卡用户根本比不出是哪一张变了)。已修。 ⚠ 冻结与解冻各有一个过渡态(freezing / unfreezing),它们不是终态 —— 过渡态期间往卡里充值会失败。别看到一条事件就认定卡已经可用。 ⚠ 同一张卡会连着来两条(freezingfrozen),务必按 status_version 向前合并 —— 乱序到达时后写的那条会把卡打回过渡态。

事件体

{
  "event_id": "evt_ca730f5b8e214d6790ab3c1e57f4d028",
  "event_type": "card.status.updated",
  "created_at": "2026-08-12T16:40:11Z",
  "merchant_id": "acme",
  "livemode": true,
  "data": {
    "object": "card",
    "id": "e70b3d41-5a28-4c96-91f0-6b2a8c05d7e3",
    "external_member_id": "u_88123",
    "status": "updated",
    "status_version": 3
  }
}

事件体字段

字段类型说明
idstring去重用这个evt_…,同一事件重投时不变。
typestring固定为 card.status.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 指南