> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thedecard.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 事件与数据结构

> 说明 Webhook 事件类型、公共消息结构、各事件的 data 字段，以及幂等处理和解密要求。配置与签名校验见Webhook 配置。

## 📄 正文

无论您要的是开卡进度、卡片状态变更、实时授权结果，还是 KYC 与工单的流转，您都可以通过 Webhook 一次性订阅——DCS 在事件发生的瞬间主动把它推送到接入机构预先登记的 `webhookUrl`，您无需轮询查询即可让自有系统与 DCS 保持实时一致。

作为持牌、自有 BIN 的发卡机构，DCS 在卡片生命周期、授权、合规等关键节点通过 Webhook 发送通知。所有事件共用同一套公共消息结构，接入机构只需实现一个接收接口即可处理全部事件类型。

> **前置条件**：在接收事件之前，您需要先登记 `webhookUrl`、配置 IP 白名单，并准备好用 `secretKey` 校验 `X-Signature` 签名。配置步骤与 HMAC-SHA256 校验代码见 [Webhook 配置](./configuration)。

***

## 核心概念：统一消息结构，多种事件类型

每一条 Webhook 都使用相同的**公共消息结构**：外层字段说明事件类型、关联对象和发生时间，`data` 则承载该事件专属的业务数据。接收端先读取 `webhookType`，再据此解析对应的 `data` 结构即可。

<Frame>
  <img className="block dark:hidden" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/diagrams/pa-webhook-dispatch-light.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=0622139418005c890a3efd16cc74b4c7" alt="Webhook 校验与按事件类型分发" width="714" height="396" data-path="imgs/diagrams/pa-webhook-dispatch-light.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/diagrams/pa-webhook-dispatch-dark.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=6b1c472e3fc3b918ffdc2e03378086ee" alt="Webhook 校验与按事件类型分发" width="714" height="396" data-path="imgs/diagrams/pa-webhook-dispatch-dark.svg" />
</Frame>

> **幂等去重**：同一事件可能因网络重试被推送多次。请使用消息中的 `webhookId` 作为幂等键；对于已处理的 `webhookId`，直接确认并跳过，避免重复入账或重复发卡。

***

## 公共消息结构

所有 Webhook 共享以下外层字段：

| 字段                 | 类型     | 必有 | 说明                                                                      |
| :----------------- | :----- | :- | :---------------------------------------------------------------------- |
| `webhookId`        | string | 是  | Webhook 唯一 ID，**用作幂等去重键**                                               |
| `webhookType`      | string | 是  | 事件大类，见下方枚举                                                              |
| `webhookSubType`   | string | 是  | 事件子类型：`CREATE`（创建）/ `UPDATE`（更新）                                        |
| `businessId`       | string | 是  | 事件所关联对象的 ID，含义随 `webhookType` 而变（如 `cardOrderId` / `cardId` / `authId`） |
| `data`             | object | 是  | 事件专属业务数据，结构见下文各节                                                        |
| `notificationTime` | string | 是  | 通知时间，格式 `yyyy-MM-dd'T'HH:mm:ss+08:00`                                   |

> **时区说明**：`notificationTime` 及各 `data` 内的 `createTime`/`modifyTime` 均带 `+08:00` 偏移（新加坡时区），与全站业务时间字段的 UTC+8 约定一致。解析时请按字符串自带的偏移量处理，不要假定为 UTC；如需确认其他时间字段的时区，请联系 DCS 接入团队。

### Webhook 事件类型（`webhookType`）

| 枚举值                           | 事件含义                       | `data` 结构                                          |
| :---------------------------- | :------------------------- | :------------------------------------------------- |
| `CARD_ORDER`                  | 卡订单（开卡/转实体/换卡）进度变更         | [卡订单](#卡订单（card_order）)                            |
| `CARD`                        | 卡片状态变更                     | [卡片](#卡片（card）)                                    |
| `AUTHORISATION_RESULT`        | 授权结果（消费/退款最终入账方向）          | [授权结果](#授权结果（authorisation_result）)                |
| `AUTHORISATION_3DS_CHALLENGE` | 3DS 验证通知                   | [3DS 验证通知](#3ds-验证通知（authorisation_3ds_challenge）) |
| `KYC_TICKET`                  | KYC 认证状态变更                 | [KYC 工单](#kyc-工单（kyc-ticket）)                      |
| `KYC`                         | 用户级 KYC 事件（资料到期需更新 / 更新完成） | [KYC](#kyc)                                        |

> `AUTHORISATION_RESULT` 是授权**结果回执**，与持卡人刷卡时需要接入机构同步决策的**授权转发请求**是两回事——后者通过 `authUrl`（RSA 双向签名）实时下发并要求接入机构在限定时间内返回放行/拒绝。授权转发请求的处理见[授权转发](../transactions/authorization)。

***

## 各事件的数据结构（`data`）

### 卡订单（`CARD_ORDER`）

`webhookType = CARD_ORDER`，对应开卡、虚拟卡转实体卡、换卡等卡订单的进度推送。

| 字段              | 类型      | 说明                                                            |
| :-------------- | :------ | :------------------------------------------------------------ |
| `cardOrderId`   | string  | 卡订单 ID                                                        |
| `profileId`     | string  | 卡配置 ID                                                        |
| `type`          | string  | 卡订单类型：`VIRTUAL` / `VIRTUAL_TO_PHYSICAL` / `REPLACEMENT`       |
| `customerId`    | string  | 用户 ID                                                         |
| `cardId`        | string  | 卡 ID，**当且仅当 `status=COMPLETED` 时才有值**                         |
| `status`        | string  | 卡订单状态，取值随 `type` 而异，见下表                                       |
| `errorCode`     | string  | 错误码（失败时）                                                      |
| `errorReason`   | string  | 错误原因（失败时）                                                     |
| `cardOrderRef`  | string  | 卡订单幂等字段（接入机构创建订单时传入的引用）                                       |
| `needExtraInfo` | boolean | 是否需补充问卷资料（KYC 需补件时为 `true`，`type=VIRTUAL` 时使用）                |
| `replaceCardId` | string  | 被替换的 `cardId`（`type=VIRTUAL_TO_PHYSICAL` / `REPLACEMENT` 时使用） |
| `createTime`    | string  | 创建时间，`yyyy-MM-dd'T'HH:mm:ss+08:00`                            |
| `modifyTime`    | string  | 最后更新时间，同上格式                                                   |

**`status` 随 `type` 取值**：

| `type`                | 状态序列                                                                                               |
| :-------------------- | :------------------------------------------------------------------------------------------------- |
| `VIRTUAL`             | `PENDING` → `CUSTOMER_PASS` → `KYC_PASS` → `CHANNEL_CUSTOMER_PASS` → `COMPLETED`（成功）/ `FAILED`（失败） |
| `VIRTUAL_TO_PHYSICAL` | `PENDING` → `PHYSICAL_SETTING_COMPLETED` → `COMPLETED` / `FAILED`                                  |
| `REPLACEMENT`         | `PENDING` → `COMPLETED` / `FAILED`                                                                 |

> 卡订单的完整状态机、`errorCode` 含义与补件动作，见[开卡](../cards/card-issuing)与[卡订单错误码](../cards/card-order-codes)。

### 卡片（`CARD`）

`webhookType = CARD`，卡片自身状态发生变更时推送（如冻结、被阻止、注销）。

| 字段             | 类型     | 说明                                                                        |
| :------------- | :----- | :------------------------------------------------------------------------ |
| `cardId`       | string | 卡 ID                                                                      |
| `enterpriseId` | string | 企业 ID                                                                     |
| `profileId`    | string | 卡配置 ID                                                                    |
| `type`         | string | 卡类型：`PHYSICAL` / `VIRTUAL`                                                |
| `customerId`   | string | 用户 ID                                                                     |
| `status`       | string | 卡状态：`PENDING_ACTIVATION` / `ACTIVATED` / `FROZEN` / `BLOCKED` / `INVALID` |
| `errorCode`    | string | 错误码                                                                       |
| `errorReason`  | string | 错误原因                                                                      |
| `panFirst6`    | string | 卡号前 6 位                                                                   |
| `panLast4`     | string | 卡号后 4 位                                                                   |
| `createTime`   | string | 创建时间，`yyyy-MM-dd'T'HH:mm:ss+08:00`                                        |
| `modifyTime`   | string | 最后更新时间，同上格式                                                               |

> Webhook 中**不含**完整卡号、CVV 等敏感信息。卡状态机（`FROZEN` 可由接入机构解冻、`BLOCKED` 仅 DCS 可解除）与 `statusReason` 详见[卡管理](../cards/card-management)。

### 授权结果（`AUTHORISATION_RESULT`）

`webhookType = AUTHORISATION_RESULT`，授权落账结果回执。

| 字段                         | 类型     | 说明                                                                       |
| :------------------------- | :----- | :----------------------------------------------------------------------- |
| `authId`                   | string | 授权 ID                                                                    |
| `approveFlag`              | string | 授权结果：`A`（approve，放行）/ `D`（decline，拒绝）                                    |
| `rejectReason`             | string | 拒绝原因（`approveFlag=D` 时）                                                  |
| `approveDate`              | string | 授权同意时间，`yyyy-MM-dd'T'HH:mm:ss+08:00`                                     |
| `cardId`                   | string | 卡 ID                                                                     |
| `direction`                | string | 资金方向：`OUTGOING`（消费/出账）/ `INCOMING`（退款/冲正/入账）                             |
| `authType`                 | string | 授权类型：`NORMAL` / `FORCE_AUTH` / `EXPIRED_RELEASE` / `STATUS_DIFF_RELEASE` |
| `outsId`                   | string | 账单（Outstanding）ID，串联授权与后续清算                                              |
| `originalAuthId`           | string | 原始授权 ID，关联原授权记录（冲正/部分撤销场景）                                               |
| `customerId`               | string | 用户 ID                                                                    |
| `currency`                 | string | 交易币种                                                                     |
| `amount`                   | number | 交易金额                                                                     |
| `acquirerCurrency`         | string | 收单币种                                                                     |
| `acquirerAmount`           | number | 收单金额                                                                     |
| `cardAcceptorNameLocation` | string | 商户名称 + 地址                                                                |
| `merchantType`             | string | MCC（商户类别码）                                                               |
| `transactionType`          | string | 交易类型                                                                     |

> **同意与拒绝都会推送**：无论授权被放行还是被拒绝，DCS 都会推送本事件（`approveFlag=A`/`D`）。接入机构在 `authUrl` 返回拒绝（`01`/`11`/`21`）会推送 `approveFlag=D`；DCS 前置校验失败（如卡冻结、注销，此时未转发到 `authUrl`）同样推送 `approveFlag=D`。因此接入机构可凭本 Webhook 建立全量授权台账，次日的授权报告可作为对账兜底。
>
> 各 `authType`、`direction` 与 `originalAuthId` 在退货、增量授权、多笔清算等场景下的组合关系，见[授权与清算](../transactions/auth-and-settlement)与[清算场景](../transactions/capture-scenarios)。

### 3DS 验证通知（`AUTHORISATION_3DS_CHALLENGE`）

`webhookType = AUTHORISATION_3DS_CHALLENGE`，3DS 挑战通知。通过 `flowsType` 区分两种认证模式：

* **`OOB`（Out Of Band）**：引导持卡人到接入机构侧完成验证。接入机构收到此 Webhook 后，需在自有应用或其他渠道中引导持卡人完成认证，再通过 API 将认证结果返回 DCS。
* **`OTP_DELEGATE`**：由接入机构向持卡人发送短信/邮件验证码。DCS 通过本 Webhook 把验证码推送给接入机构，接入机构**解密后**再发送给持卡人。

| 字段                    | 类型     | 说明                                                           |
| :-------------------- | :----- | :----------------------------------------------------------- |
| `challengeId`         | string | 3DS 挑战 ID                                                    |
| `status`              | string | 状态：`INIT` / `NOTICED` / `RECEIVED` / `APPROVED` / `REJECTED` |
| `cardId`              | string | 卡 ID                                                         |
| `expiryTime`          | string | 挑战过期时间                                                       |
| `currency`            | string | 发起交易的币种                                                      |
| `amount`              | number | 发起交易的金额                                                      |
| `merchantId`          | string | 商户 ID                                                        |
| `merchantName`        | string | 商户名称                                                         |
| `merchantCountry`     | string | 商户国家                                                         |
| `mcc`                 | string | 商户类别                                                         |
| `flowsType`           | string | 认证流程类型：`OOB` / `OTP_DELEGATE`                                |
| `challengeMethodType` | string | 挑战方法类型：`DELEGATE_SCA_V1` / `SMS_OTP` / `EMAIL_OTP`           |
| `otpPasscode`         | string | OTP 验证码（**AES-GCM 加密**），仅 `flowsType=OTP_DELEGATE` 时有值       |
| `phoneNumber`         | string | 持卡人手机号（**AES-GCM 加密**），仅 `flowsType=OTP_DELEGATE` 时有值        |
| `email`               | string | 持卡人邮箱（**AES-GCM 加密**），仅 `flowsType=OTP_DELEGATE` 时有值         |
| `iv`                  | string | AES-GCM 加密所用 IV（Base64 编码），仅 `flowsType=OTP_DELEGATE` 时有值    |

> **敏感字段解密（仅 `OTP_DELEGATE`）**：`otpPasscode`、`phoneNumber`、`email` 由 DCS 使用接入机构的 `secretKey` 经 **AES-GCM** 加密传输，Webhook 同时携带 `iv`。接入机构需用 `secretKey` + `iv` 解密后，再把验证码下发给持卡人。AES/GCM 参考实现（12 字节 IV、128 位认证标签）见[卡管理 · 重置 PIN](../cards/card-management#重置-pin)。3DS 转发的完整时序见 [3DS 转发](../transactions/3ds)。

### KYC 工单（KYC Ticket）

`webhookType = KYC_TICKET`，KYC 认证工单状态变更。

| 字段                | 类型     | 说明                                                                                                      |
| :---------------- | :----- | :------------------------------------------------------------------------------------------------------ |
| `kycTicketId`     | string | KYC 工单 ID                                                                                               |
| `kycTicketRef`    | string | KYC 工单幂等号                                                                                               |
| `customerId`      | string | 用户 ID                                                                                                   |
| `kycTicketStatus` | string | 工单状态：`INIT` / `NEED_VERIFY` / `PENDING` / `PASSED` / `REJECTED`                                         |
| `kycApplyMode`    | string | 申请方式：`API`（API 提交）/ `H5`（H5 页面申请 KYC）/ `H5-RENEWAL`（更新 KYC 资料）/ `H5-MIGRATION`（KYC 信息迁移）。用于区分这条通知属于哪条流程 |
| `errorCode`       | string | 错误码，**仅 `REJECTED` 状态返回**                                                                               |
| `errorMessage`    | string | 错误描述，**仅 `REJECTED` 状态返回**                                                                              |

> 不同 `kycApplyMode` 下工单可能出现的状态并不相同：`H5-RENEWAL` 与 `H5-MIGRATION` 只有 `INIT` / `PASSED` / `REJECTED`，不会出现 `NEED_VERIFY` 或 `PENDING`。各模式的状态线见[更新 KYC 资料](../kyc/kyc-renewal)与 [KYC 信息迁移](../kyc/kyc-migration)。
>
> KYC 状态机、拒绝码与补件动作，见[查询 KYC](../kyc/query-kyc)与 [KYC 拒绝码](../kyc/kyc-reject-codes)。

### KYC

`webhookType = KYC`，**用户级** KYC 事件：DCS 检测到该用户证件 / KYC 资料到期需要更新，或更新已完成。与 `KYC_TICKET`（工单级，跟单次认证的进度）是两回事。

| 字段                   | 类型        | 说明                                                |
| :------------------- | :-------- | :------------------------------------------------ |
| `customerId`         | string    | 用户 ID                                             |
| `kycRenewalRequired` | boolean   | `true` = 需要更新 KYC 资料；`false` = 更新已完成，限制解除         |
| `kycRenewalType`     | string\[] | 需要更新的因子：`POI`（身份证明）/ `SELFIE`（人脸）。可用于在前端只提示该补的那一项 |

> 收到 `kycRenewalRequired=true` 后如何引导用户更新，见[更新 KYC 资料](../kyc/kyc-renewal)；也可随时主动调[查询用户 KYC 信息](../kyc/kyc-info)确认。

***

## 一条完整 Webhook 示例

以「虚拟卡开卡完成」为例，接入机构收到的 HTTP POST body 形如：

```json theme={null}
{
  "businessId": "co_1234567890",
  "data": {
    "cardId": "card_xxx",
    "cardOrderId": "co_1234567890",
    "cardOrderRef": "your-ref-001",
    "createTime": "2026-01-01T11:58:00+08:00",
    "customerId": "cus_xxx",
    "modifyTime": "2026-01-01T12:00:00+08:00",
    "needExtraInfo": false,
    "profileId": "prof_xxx",
    "status": "COMPLETED",
    "type": "VIRTUAL"
  },
  "notificationTime": "2026-01-01T12:00:00+08:00",
  "webhookId": "wh_7f3a1c9e",
  "webhookSubType": "UPDATE",
  "webhookType": "CARD_ORDER"
}
```

> 示例已按实际报文的字段字典序排列，仅为便于阅读做了缩进格式化，实际报文为紧凑格式（见下方[注意事项](#注意事项)）。

请求头中携带 `X-Signature`（HMAC-SHA256，针对整个 body 计算）。接入机构应：

1. 先校验 `X-Signature`，校验失败即丢弃；
2. 用 `webhookId` 去重；
3. 按 `webhookType` 分发到对应处理逻辑；
4. 返回 HTTP 200（服务端以 2xx 成功响应判定接收成功），否则 DCS 会按重试策略重投。

> 除实时 `AUTHORISATION` 外，通用 Webhook 最多重试 3 次；重试由定时任务驱动，退避间隔随任务配置而定。实时 `AUTHORISATION` 不重试，超时直接按 `responseCode=21` 处理。DCS 出站 `Content-Type` 固定为 `application/json`。

***

## 注意事项

### 报文格式与字段顺序

Webhook 报文以紧凑 JSON（无空格、无换行）发送，所有层级（公共消息结构、`data` 及其嵌套对象）的字段均按**完整字段名的字典序**升序排列——逐字符比较字符编码，首字符相同则比较下一个字符，区分大小写。该顺序是 DCS 侧序列化的确定性保证，便于必要时核对原始报文；接入机构按 JSON 正常解析即可，**不应依赖字段顺序取值**。

### 字段扩展说明

Webhook 报文（各事件的 `data`）后续可能会新增字段，字段扩展遵循以下兼容性承诺：

1. **存量字段保持稳定**：当前文档已定义的字段，其名称、类型、含义永不变更，不会删除；
2. **仅以新增字段的方式扩展**：接入机构可按需读取新增字段；暂不需要时忽略即可，不影响既有解析；
3. **解析时需忽略未知字段**：请勿使用「遇到未知字段即报错」的严格校验模式解析 Webhook 报文，确保新增字段不影响存量对接。

### 新增字段与验签

`X-Signature` 基于 DCS 实际发送的**原始消息体字符串**计算。请直接使用接收到的原始 body 计算 HMAC-SHA256 并比对，不要将报文解析后重新生成 JSON 再验签——重新生成会因字段缺失、字段顺序或格式差异导致验签失败。只要基于原始报文验签，新增字段不会影响验签结果。校验方法见 [Webhook 配置](./configuration)。

***

## 下一步

* 还没配置签名校验？请先完成 [Webhook 配置](./configuration)，拿到可校验 `X-Signature` 的接收端。
* 需要在持卡人刷卡时实时决策放行/拒绝？请阅读[授权转发](../transactions/authorization)。
