Z Zise Developers

card.transaction.updated

卡交易已接收或状态已更新

何时发

正常交易回调、自动巡检或运营查询补录处理完成后发送。 data.id 是交易列表中的 ctx_ 资源 ID, data.card_id 是 crd_ 卡片 ID;通过 GET /v1/cards/{id}/transactions 回查。 authorized 表示已授权、待结算,不能按已结算处理。 同一本地交易的相同快照使用相同 event_id,接收方须按 event_id 去重。 沙盒交易模拟走相同处理流程,通知按沙盒环境投递。 bank_fee 为银行手续费,zinfra_fee 为 zinfra 手续费(当前所有卡为 "0")。 均为最小单位整数串,币种与精度分别为 fee_currency、fee_scale。 这是交易累计费用快照,不是每次通知新增收费;授权费用可能在结算时更新。 original_amount / original_currency 是上游交易原币原文;bill_amount 是卡本币 账单(最小单位整数串,与列表同一套取值:已存结算额或授权占用,不现场折汇)。 没有已知账单时省略 bill_amount。详情以 GET /v1/cards/{id}/transactions 为准。

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

由 cron 或上游回调触发。

事件体

{
  "event_id": "evt_06a0d3fbbe074aa98809e2b90588b123",
  "event_type": "card.transaction.updated",
  "created_at": "2026-09-08T05:45:00Z",
  "merchant_id": "acme",
  "livemode": true,
  "data": {
    "object": "card_transaction",
    "id": "ctx_11111111-2222-4333-8444-555555555555",
    "card_id": "crd_aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee",
    "external_member_id": "u_88123",
    "status": "authorized",
    "status_version": 1788846300000,
    "bank_fee": "30000",
    "zinfra_fee": "0",
    "fee_currency": "USD",
    "fee_scale": 6,
    "original_amount": "8.0000",
    "original_currency": "HKD",
    "bill_amount": "1020000"
  }
}

事件体字段

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