> ## 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 与 WebSocket 实时通知

> DCS 把 KYC 状态、资产变动、卡交易、订单状态等关键事件实时同步给您，有两条互补通道——Webhook（DCS 主动推送到您的回调 URL） 与 WebSocket（您主动订阅私有频道、可按版本回溯）。两条通道共享同一套 9 类业务事件与 data 结构，仅消息结构（字段名/类型）不同。WebSocket 是 DeCard 托管 的特色能力。

无论您是交易所、钱包还是平台方，只要用户的 KYC、余额、卡或订单发生变化，DCS 都会通过实时通道告知您，避免轮询。本页帮您把两条通道一次配通：

* **Webhook**：推送式。您提供一个 HTTPS 回调地址，DCS 在事件发生时把签名后的 JSON 推送到该地址。适合服务端到服务端的稳定接收。
* **WebSocket**：订阅式（DeCard 托管特色）。您先获取一个专属私有频道，连上 `wss` 长连接实时收消息；漏收时还能按 `version` 回溯历史。适合需要前端/网关实时感知、或对补偿回溯有要求的场景。

<Warning>
  隐私红线：DeCard 托管模式涉及大量终端用户隐私数据。本页所有示例中的 `externalUserId`、卡号、商户名、地址、`txHash`、`secretKey` 等**均为占位/脱敏值**，请勿当作真实数据；您的接收端日志也应脱敏，切勿明文记录真实 PII 与密钥。
</Warning>

***

## 1. Webhook（推送式）

### 1.1 核心能力

* **自动重试**：通知失败时 DCS 自动重试，**最多 3 次**。
* **签名验证**：每条通知都在请求头携带 `X-Signature` 数字签名，确保来源可信。
* **幂等处理**：每个事件携带全局唯一 `webhookId`，便于您去重。

### 1.2 前置条件与接入流程

#### 步骤 1：实现 Webhook 接收端

您的服务需暴露一个 `POST` 接口，满足以下要求：

* 支持 `application/json` 请求体；
* 返回 `200 OK` 表示接收成功；返回**其他任何状态码**都会触发 DCS 重试；
* **必须在 2 秒内返回响应**（DCS 侧 connectTimeout 与 socketTimeout 均为 2000 ms），否则视为超时失败（同样会触发重试）。

#### 步骤 2：同步 DCS 配置回调 URL

把步骤 1 实现的回调 URL 同步给 DCS 团队。该 URL **必须为 HTTPS 且公网可访问**。

#### 步骤 3：验证与上线

先在 UAT（沙盒）环境验证，验证通过后再上线生产。

### 1.3 推送时序与重试

<Frame>
  <img className="block dark:hidden" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/diagrams/va-webhook-retry-light.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=830fec3719cd5a3f958e7b25620f4e5f" alt="Webhook 推送时序与重试" width="560" height="628" data-path="imgs/diagrams/va-webhook-retry-light.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/diagrams/va-webhook-retry-dark.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=5810bb2e4c387471ea0f7d39beb9c4a9" alt="Webhook 推送时序与重试" width="560" height="628" data-path="imgs/diagrams/va-webhook-retry-dark.svg" />
</Frame>

### 1.4 安全：`X-Signature` 验签

为确保通信安全，DCS 在每个 Webhook 请求头中携带 `X-Signature`。该签名由 <b>HmacSHA256</b> 算法生成：DCS 用您的 `SecretKey` 对<b>原始消息体（raw body）</b>计算签名。您收到后需用同一 `SecretKey` 对原始 body 复算，并与 `X-Signature` 比对，验证请求的真实性与完整性。

<Warning>
  签名头名固定为 `X-Signature`，HMAC 密钥为您的 `SecretKey`（不是 `apiKey`）。请按此约定实现验签。
</Warning>

#### 请求头（Headers）

| Header         | 说明                                          |
| :------------- | :------------------------------------------ |
| `X-Signature`  | 签名字符串（HmacSHA256，用 `SecretKey` 对原始 body 计算） |
| `Content-Type` | 固定为 `application/json`                      |

#### 签名计算参考（Java）

```java theme={null}
import org.apache.commons.codec.binary.Hex;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

class HmacSignature {
    public static void main(String[] args) throws Exception {
        String secretKey = "<your-secret-key>";              // 占位，请从密钥配置中心读取
        String webhookPayloadStr = "<raw-webhook-body-string>"; // 原始 JSON 字符串，不可先反序列化再拼回
        SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(keySpec);
        byte[] hmac = mac.doFinal(webhookPayloadStr.getBytes());
        System.out.println(Hex.encodeHexString(hmac));        // 与 X-Signature 比对
    }
}
```

#### 接收端参考（Java，含 7 步处理约定）

```java theme={null}
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

@RestController
public class WebhookController {

    private static final String HMAC_SHA256 = "HmacSHA256";
    // 仅做示意，建议从密钥配置中心获取
    private static final String apiSecret = "<your-secret-key>";

    @PostMapping("/your-path/webhook")
    public ResponseEntity<String> processWebhook(
            @RequestHeader("X-Signature") String signature,
            @RequestBody String rawBody // 必须用 String 接收原始 JSON，不能直接转对象！
    ) {
        // 1. 验证签名（用原始 rawBody 复算并与 X-Signature 比对）
        if (!verifySignature(rawBody, signature)) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid signature");
        }
        // 2. 解析 rawBody 获取 webhookId、type 等字段
        // 3. 若该 type 不是您关心的，可忽略并直接返回 200，结束流程
        // 4. 幂等处理：检查 webhookId 是否已处理
        // 5. 解析业务数据（可选：转为具体对象）
        // 6. 处理业务逻辑（建议异步，避免拖过 2 秒超时）
        // 7. 标记 webhookId 为已处理（幂等）
        return ResponseEntity.ok("OK");
    }

    private boolean verifySignature(String payloadStr, String signature) {
        return StringUtils.equals(signature, getSignature(payloadStr));
    }

    private String getSignature(String data) {
        try {
            SecretKeySpec keySpec = new SecretKeySpec(apiSecret.getBytes(), HMAC_SHA256);
            Mac mac = Mac.getInstance(HMAC_SHA256);
            mac.init(keySpec);
            return Hex.encodeHexString(mac.doFinal(data.getBytes()));
        } catch (Exception e) {
            throw new RuntimeException("Failed to calculate hmac-sha256", e);
        }
    }
}
```

### 1.5 Webhook 公共结构

<Warning>
  注意类型与字段差异：Webhook 用 `notificationTimestamp` / `eventTimestamp`（**`Long`，毫秒**）；金额/数量类字段（如 `freeDelta`、`transactionAmount`）为 `BigDecimal`、`tranId` 为 `Long`。**WebSocket 的对应字段是 `string`、且时间字段名为 `timestamp`**（见 [第 2.4 节](#2-4-websocket-公共结构)），合并阅读时切勿混用类型。
</Warning>

| 名称                      | 类型     | 描述                                    |
| :---------------------- | :----- | :------------------------------------ |
| `webhookId`             | string | 全局唯一 webhook ID，可用于幂等去重               |
| `type`                  | string | 通知类型（见 [第 3 节事件总表](#3-业务事件总表（两通道共享）)） |
| `externalUserId`        | string | 外部用户 ID                               |
| `notificationTimestamp` | Long   | 通知发送时间，Unix 时间戳（毫秒）                   |
| `eventTimestamp`        | Long   | 事件落表时间，Unix 时间戳（毫秒）                   |
| `data`                  | object | 事件具体业务数据，结构依 `type` 而定                |

**Webhook 通知示例（资产变动，脱敏）：**

```json theme={null}
{
  "webhookId": "4862356405917483776",
  "type": "BALANCE_CHANGE",
  "externalUserId": "d8ef852e-****-****-****-************",
  "notificationTimestamp": 1767777763336,
  "eventTimestamp": 1767777641000,
  "data": {
    "asset": "USD",
    "network": "",
    "freeDelta": -2.92,
    "freezeDelta": 2.92,
    "tranId": 4862356405699379969,
    "externalTranId": "4862356404793410306",
    "free": 0,
    "freeze": 18.1,
    "type": "CONVERSION"
  }
}
```

***

## 2. WebSocket（订阅式，★ DeCard 托管特色）

> WebSocket 通道是 DeCard 托管独有能力——您可订阅一个专属私有频道实时收消息，并按 `version` 回溯漏收。

WebSocket 实时推送用于同步用户相关数据变化，覆盖 KYC 状态、资产变动、卡交易、订单状态等关键信息，提升您对用户操作的响应效率。

### 2.1 前置条件与三步接入

1. **创建监听频道**：调 `GET /websocket/v1/get-channel` 为机构生成专属私有频道（频道串即接收消息的唯一标识，例如 `HBHmMK99SJEVMVUqCb4`）。
2. **连接 `wss` 长连接**：把频道串拼到 `wss` 地址的 `{channel}` 处，按环境选择主网/测试网（见 [第 2.3 节](#2-3-websocket-环境地址)），建立连接并监听消息。
3. **频道轮换 + 漏收回溯**：每个频道**存活 24 小时**，过期失效；为避免消息丢失，请在过期前重新调 `get-channel` 取新频道。建议在频道到期前 1 小时重新调用 `get-channel` 获取新频道，确保新旧频道有足够的重叠窗口完成切换。漏收或需回溯时，用 `GET /websocket/v1/search` 按 `version` 查历史消息。

### 2.2 频道生命周期

<Frame>
  <img className="block dark:hidden" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/diagrams/va-ws-lifecycle-light.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=d281ae9875b61a180221df8119897e62" alt="WebSocket 频道生命周期" width="682" height="480" data-path="imgs/diagrams/va-ws-lifecycle-light.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/diagrams/va-ws-lifecycle-dark.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=ab904981453ab4b45182c3de1539842d" alt="WebSocket 频道生命周期" width="682" height="480" data-path="imgs/diagrams/va-ws-lifecycle-dark.svg" />
</Frame>

<Warning>
  频道安全机制：**新频道生成后，旧频道不再推送数据**。请确保切换时机正确，避免在旧频道上空等。
</Warning>

### 2.3 WebSocket 环境地址

| 环境     | URL                                       |
| :----- | :---------------------------------------- |
| 主网（生产） | `wss://stream.thedecard.com/ws/{channel}` |
| 测试（沙盒） | `wss://stream.uatdcd.com/ws/{channel}`    |

> `{channel}` 用 `get-channel` 返回的频道串替换。

### 2.4 WebSocket 公共结构

<Warning>
  与 Webhook 结构的差异：WebSocket 用 **`timestamp`（`string`）**，且**多一个 `version` 字段**；`data` 内各业务字段（金额/数量/`tranId` 等）均为 **`string`**。这与 Webhook 的 `Long` / `BigDecimal` 类型不同，请按本表为准。
</Warning>

| 名称               | 类型     | 描述                                          |
| :--------------- | :----- | :------------------------------------------ |
| `type`           | string | 消息类型（见 [第 3 节事件总表](#3-业务事件总表（两通道共享）)）       |
| `timestamp`      | string | 消息时间戳                                       |
| `version`        | string | 消息版本号，可用于 `GET /websocket/v1/search` 回溯历史消息 |
| `externalUserId` | string | 用户 ID                                       |
| `data`           | object | 事件具体业务数据，结构依 `type` 而定                      |

**WebSocket 消息示例（资产变动，脱敏）：**

```json theme={null}
{
  "type": "BALANCE_CHANGE",
  "timestamp": "1733980483134",
  "version": "2",
  "externalUserId": "9f8b5f82-****-****-****-************",
  "data": {
    "asset": "USD",
    "network": "",
    "freeDelta": "12.64",
    "freezeDelta": "0",
    "tranId": "4285261023005170688",
    "externalTranId": "4285261022921284608",
    "free": "12.64",
    "freeze": "0",
    "type": ""
  }
}
```

### 2.5 WebSocket 接口参考

两个接口均返回全站统一响应结构：

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": null,
  "messageDetail": {
    "message": "",
    "title": "",
    "type": "",
    "icon": "",
    "action": "",
    "linkTitle": "",
    "linkUrl": ""
  },
  "data": "<string>"
}
```

| 字段              | 说明                                                 |
| :-------------- | :------------------------------------------------- |
| `code`          | 业务状态码，成功为 `SYS_SUCCESS`（两种模式一致），失败为具体错误码           |
| `message`       | 简要信息，成功时通常为 `null`                                 |
| `messageDetail` | 详细信息对象，通常各子字段为空                                    |
| `data`          | string。`get-channel` 返回频道标识串；`search` 返回查询到的历史消息内容 |

> 判断成功请以 `code == "SYS_SUCCESS"` 为准；响应结构**不含** `success` 布尔字段。

#### 获取监听频道

```
GET /websocket/v1/get-channel
```

无请求参数。`data` 返回机构专属的私有频道标识串。

#### 按版本查询历史消息

```
GET /websocket/v1/search?version=<version>
```

| 参数        | 位置    | 必填       | 说明                                                                                 |
| :-------- | :---- | :------- | :--------------------------------------------------------------------------------- |
| `version` | query | REQUIRED | 消息版本号；返回该版本对应的历史消息，用于漏收回溯。version 取值即 WebSocket 实时消息推送中的 `version` 字段（参见第 2.4 节示例） |

> 这两个 REST 接口与全站其余 REST 接口一致，请按 [鉴权指南](./overview) 携带签名头（`X-DAPI-API-KEY` / `X-DAPI-TIMESTAMP` / `X-DAPI-NONCE` / `X-DAPI-SIGN`）。

***

## 3. 业务事件总表（两通道共享）

两条通道**共享同一套业务事件类型与 `data` 结构**，共 **8 类**。仅消息格式不同：Webhook 用 `notificationTimestamp` / `eventTimestamp`（`Long`），WebSocket 用 `timestamp` + `version`（`string`），且 `data` 内字段类型如前述（Webhook `BigDecimal`/`Long` ↔ WebSocket `string`）。

| `type`                        | 含义            | `data` 关键字段 / 状态机                                                                                     |
| :---------------------------- | :------------ | :---------------------------------------------------------------------------------------------------- |
| `BALANCE_CHANGE`              | 数字资产变动        | `asset` / `freeDelta` / `freezeDelta` / `free` / `freeze` / `tranId` / `externalTranId` / `type`      |
| `CARD_TRANSACTION`            | 卡交易记录         | 见 [3.2](#3-2-card_transaction-卡交易记录)，含 `direction`、`transactionType`、`originalExternalTranId`（仅反交易场景） |
| `CARD_TRANSACTION_SETTLEMENT` | 卡清算记录         | `settledAmount` / `settledCurrencyCode` / `transactionType` 等                                         |
| `CARD_PHYSICAL_SHIPPING`      | 实体卡物流信息       | `status`（待制卡/制卡中/邮寄中/邮寄完成）/ `trackingNumber` / `trackingCompanyName`                                  |
| `ORDER_STATUS`                | FOMO 充值（订单）状态 | `txHash` / `transStatus`（5 枚举）/ `asset` / `creditCurrency` / `convertCurrency` 等                      |
| `QR_ORDER_STATUS`             | 扫码付订单状态       | `orderId` / `orderStatus`（7 枚举）                                                                       |
| `CARD_APPLY`                  | 卡片申请状态        | `applyId` / `status`（PENDING/SUCCEED/FAILED）/ `needExtraInfo`                                         |
| `CARD_STATUS`                 | 卡状态变更         | `cardId` / `cardMantissa` / `cardStatus`（NORMAL/FROZEN/CANCELLED）                                     |

> 术语说明：`CARD_APPLY` 的最终状态 `SUCCEED` 是**卡申请**状态（枚举为 `SUCCEED` 而非 `SUCCESS`），与卡订单和卡片的状态定义不同，请勿混淆。`CARD_STATUS` 的 `NORMAL/FROZEN/CANCELLED` 与 [卡管理](../how-to-use/managing-cards/overview) 中的卡状态定义一致。

### 3.1 `BALANCE_CHANGE` 数字资产变动

| 名称               | 类型（WS / Webhook）    | 描述       |
| :--------------- | :------------------ | :------- |
| `tranId`         | string / Long       | 流水单号     |
| `externalTranId` | string              | 外部交易 ID  |
| `asset`          | string              | 资产币种     |
| `network`        | string              | 网络       |
| `freeDelta`      | string / BigDecimal | 资产变动数量   |
| `freezeDelta`    | string / BigDecimal | 资产冻结变动数量 |
| `free`           | string / BigDecimal | 可用持仓     |
| `freeze`         | string / BigDecimal | 冻结资产     |
| `type`           | string              | 变动类型     |

### 3.2 `CARD_TRANSACTION` 卡交易记录

| 名称                         | 类型（WS / Webhook）    | 描述                                               |
| :------------------------- | :------------------ | :----------------------------------------------- |
| `cardNumber`               | string              | 卡号后 4 位                                          |
| `transactionCurrencyCode`  | string              | 交易币种 ISO 代码（如 `840`、`702`）                       |
| `transactionAmount`        | string / BigDecimal | 交易金额                                             |
| `localTransactionDate`     | string              | 交易日期（如 `1211`）                                   |
| `localTransactionTime`     | string              | 交易时间（如 `161420`）                                 |
| `response`                 | string              | 交易结果（A=accept/success，D=deny/fail）               |
| `externalTranId`           | string / Long       | 外部交易 ID                                          |
| `systemTraceAuditNumber`   | string              | 系统跟踪号                                            |
| `requestAmountInUsd`       | string              | 美元交易金额                                           |
| `mcc`                      | string              | 商户类别码                                            |
| `cardAcceptorNameLocation` | string              | 商户名称和位置信息                                        |
| `direction`                | string              | 资金流向：`DEBIT`（扣款/消费）、`CREDIT`（入账/退款）              |
| `settlementCurrencyCode`   | string              | 卡账户记账币种（ISO 4217）                                |
| `settlementAmount`         | string / BigDecimal | 卡账户记账金额                                          |
| `transactionType`          | string              | 交易类型：R=卡消费，C=ATM 提现，Q=查询类，P=转账或退款                |
| `merchantCountryCode`      | string              | 商户国家代码（3 位数字）                                    |
| `originalExternalTranId`   | string              | 原始交易的外部交易 ID，**仅反交易场景返回**（撤销、冲正、退款等）；普通授权交易不含此字段 |

> 当前 `CARD_TRANSACTION` 只通过 `response=A/D` 区分批准与拒绝，不携带更细的拒绝原因码；交易查询侧也没有 `declinedReason` / `declineCode` 字段。若需要细分原因，需 DCS 侧后续系统支持（请与 DCS 确认排期）；现阶段按余额、卡状态与风控/MCC 逐项排查。

**示例（退款，含 `originalExternalTranId`，脱敏）：**

```json theme={null}
{
  "type": "CARD_TRANSACTION",
  "version": "562",
  "timestamp": "1766134300000",
  "externalUserId": "d8ef852e-****-****-****-************",
  "data": {
    "response": "A",
    "direction": "CREDIT",
    "cardNumber": "**** **** **** 0000",
    "externalTranId": "4834788305299456000",
    "originalExternalTranId": "4834788305187471104",
    "settlementAmount": "51",
    "transactionAmount": "51",
    "requestAmountInUsd": "0",
    "localTransactionDate": "1219",
    "localTransactionTime": "170000",
    "settlementCurrencyCode": "702",
    "systemTraceAuditNumber": "185974",
    "transactionCurrencyCode": "702",
    "mcc": "4511",
    "cardAcceptorNameLocation": "DEMO MERCHANT            DEMO CITY XX",
    "transactionType": "P",
    "merchantCountryCode": "458"
  }
}
```

### 3.3 `CARD_TRANSACTION_SETTLEMENT` 卡清算记录

字段与 `CARD_TRANSACTION` 大体一致，差异点：用 `settledAmount`（清算金额）+ `settledCurrencyCode`（清算币种 ISO 4217）替代 settlement 系字段；仅含 `direction`，不含 `response` 及其他授权专有字段（如 `systemTraceAuditNumber`）。

```json theme={null}
{
  "type": "CARD_TRANSACTION_SETTLEMENT",
  "version": "561",
  "timestamp": "1766134239507",
  "externalUserId": "d8ef852e-****-****-****-************",
  "data": {
    "direction": "DEBIT",
    "cardNumber": "**** **** **** 0000",
    "settledAmount": "51",
    "externalTranId": "4834788305187471104",
    "transactionAmount": "51",
    "settledCurrencyCode": "702",
    "localTransactionDate": "1219",
    "localTransactionTime": "165028",
    "transactionCurrencyCode": "702",
    "mcc": "4511",
    "cardAcceptorNameLocation": "DEMO MERCHANT            DEMO CITY XX",
    "transactionType": "R",
    "merchantCountryCode": "458"
  }
}
```

### 3.4 `CARD_PHYSICAL_SHIPPING` 实体卡物流信息

| 名称                    | 类型     | 描述                                                                                                      |
| :-------------------- | :----- | :------------------------------------------------------------------------------------------------------ |
| `cardMantissa`        | string | 卡号后 4 位                                                                                                 |
| `status`              | string | 物流状态：`PENDING_EMBOSSING`（待制卡）、`EMBOSSING_IN_PROGRESS`（制卡中）、`IN_DELIVERY`（邮寄中）、`DELIVERY_COMPLETE`（邮寄完成） |
| `trackingNumber`      | string | 运单号（仅 `IN_DELIVERY` / `DELIVERY_COMPLETE` 状态时提供）                                                        |
| `trackingCompanyName` | string | 物流公司名称（仅 `IN_DELIVERY` / `DELIVERY_COMPLETE` 状态时提供）                                                     |

### 3.5 `ORDER_STATUS` FOMO 充值状态

| 名称                   | 类型（WS / Webhook）    | 描述                              |
| :------------------- | :------------------ | :------------------------------ |
| `txHash`             | string              | 链上交易 Hash（非链上订单可能为空）            |
| `transStatus`        | string              | 订单状态，见下方枚举                      |
| `asset`              | string              | 原始资产币种（USDC / USDT）             |
| `amount`             | string / BigDecimal | 原始交易金额                          |
| `network`            | string              | 区块链网络（法币或内部订单为空）                |
| `fromAddress`        | string              | 转出地址                            |
| `toAddress`          | string              | 转入地址                            |
| `creditCurrency`     | string              | 实际入账币种（SGD / USD / USDC / USDT） |
| `creditAmount`       | string / BigDecimal | 实际入账金额                          |
| `convertCurrency`    | string              | 兑换后币种（SGD / USD）                |
| `convertAmount`      | string / BigDecimal | 兑换后金额                           |
| `convertFeeCurrency` | string              | 兑换手续费币种（SGD / USD）              |
| `convertFeeAmount`   | string / BigDecimal | 兑换手续费金额                         |

**`transStatus` 枚举：**

| 枚举值              | 是否终态 | 描述          |
| :--------------- | :--- | :---------- |
| `PENDING_NORMAL` | 否    | 订单已创建，等待处理  |
| `PENDING_CREDIT` | 否    | 外部交易完成，等待入账 |
| `PENDING_AUDIT`  | 否    | 订单审核中       |
| `SUCCESS`        | 是    | 订单处理成功      |
| `FAILED`         | 是    | 订单处理失败      |

### 3.6 `QR_ORDER_STATUS` 扫码付订单状态

| 名称            | 类型（WS / Webhook） | 描述         |
| :------------ | :--------------- | :--------- |
| `orderId`     | string / Long    | 订单 ID      |
| `orderStatus` | string           | 订单状态，见下方枚举 |

**`orderStatus` 枚举：**

| 枚举值                    | 是否终态 | 描述      |
| :--------------------- | :--- | :------ |
| `PENDING`              | 否    | 待支付     |
| `SUCCESS`              | 是    | 订单处理成功  |
| `FAILED`               | 是    | 订单处理失败  |
| `FULL_REFUNDED`        | 是    | 全额退款成功  |
| `PARTIAL_REFUNDED`     | 是    | 部分退款成功  |
| `PENDING_CONFIRMATION` | 否    | 支付结果待确认 |
| `PROCESSING`           | 否    | 支付处理中   |

### 3.7 `CARD_APPLY` 卡片申请状态

| 名称              | 类型（WS / Webhook） | 描述                        |
| :-------------- | :--------------- | :------------------------ |
| `applyId`       | string           | 申请 ID                     |
| `categoryId`    | string / Long    | 卡类别 ID                    |
| `network`       | string           | 卡组织（如 VISA、MASTERCARD）    |
| `currency`      | string           | 币种                        |
| `applyRef`      | string           | 申请订单幂等字段                  |
| `status`        | string           | 申请状态，见下方枚举                |
| `errorCode`     | string           | 申请失败错误码（仅 `FAILED` 状态时提供） |
| `needExtraInfo` | boolean          | 是否需要用户补充资料，见下方说明          |

**`status` 枚举：**

| 枚举值       | 是否终态 | 描述                                  |
| :-------- | :--- | :---------------------------------- |
| `PENDING` | 否    | 申请中                                 |
| `SUCCEED` | 是    | 成功（卡申请终态，注意是 `SUCCEED` 非 `SUCCESS`） |
| `FAILED`  | 是    | 失败                                  |

**`needExtraInfo` 说明：**

| 取值      | 含义                                                      |
| :------ | :------------------------------------------------------ |
| `true`  | 用户尚未提交补充资料，请引导用户上传（参考 H5 引导页 `action=KYC_EXTRA_DOC`）    |
| `false` | 用户已完成提交动作；此时即使 `status` 仍为 `PENDING`，也请等待 DCS 审核，无需重复引导 |

### 3.8 `CARD_STATUS` 卡状态

当卡状态发生变更时通知最新的卡状态。目前支持虚拟卡状态（`cardStatus`），实体卡状态（`physicalCardStatus`）后续补充。

| 名称             | 类型     | 描述          |
| :------------- | :----- | :---------- |
| `cardId`       | string | 卡片 ID       |
| `cardMantissa` | string | 卡号尾号（后 4 位） |
| `cardStatus`   | string | 虚拟卡状态，见下方枚举 |

**`cardStatus` 枚举（与 [卡管理状态机](../how-to-use/managing-cards/overview) 对齐）：**

| 枚举值         | 描述 |
| :---------- | :- |
| `NORMAL`    | 正常 |
| `FROZEN`    | 冻结 |
| `CANCELLED` | 销户 |

***

## 4. 最佳实践与常见问题

* **先验签，再处理**：处理任何消息内容前，先用 `SecretKey` 对原始请求体复算 HmacSHA256 并比对 `X-Signature`；验签失败应拒绝（返回 401）并告警，切勿处理。
* **快速返回 200、业务异步化**：DCS 要求 **2 秒内**返回。收到后建议先存储（带 `webhookId`）并立即回 `200`，再异步处理业务，避免拖过超时触发不必要的重试。
* **基于 `webhookId` 幂等去重**：通知可能因重试被多次投递。请存储已处理的 `webhookId`，处理前先查重。
* **注意时序**：Webhook 可能因网络抖动或重试而乱序到达。如需按事件真实发生顺序处理，请以 `eventTimestamp`（事件落表时间，见第 1.5 节）而非接收时间排序，必要时设置一个短缓冲窗口（如 30 秒）再批量处理。
* **忽略您不关心的 `type`**：对不关心的事件类型，直接返回 `200` 忽略即可，不要返回错误码（错误码会触发重试）。
* **WebSocket 提前轮换频道**：频道仅存活 24 小时且新频道生成后旧频道停推，请在过期前重新 `get-channel` 并平滑切换。
* **用 `version` 补查**：WebSocket 连接因网络抖动可能漏收，定期对账后用 `GET /websocket/v1/search?version=` 回溯补齐。
* **两条通道按需取舍**：对接收稳定性优先选 Webhook；对实时性/前端感知/回溯能力有要求可加用 WebSocket，二者事件语义一致。
* **不会通过实时通道下发敏感卡数据**：通知中卡号仅为后 4 位（脱敏），不含完整 PAN / CVV；获取卡敏感信息请走 `action=CARD_INFO` 托管引导页（见 [查看加密卡信息](../how-to-use/managing-cards/viewing-encrypted-card-details)）。

***

## 下一步 / 相关

* [鉴权指南](./overview)：WebSocket 两个 REST 接口与全站其余 REST 接口一致，按本指南携带签名头。
* [卡管理](../how-to-use/managing-cards/overview)：`CARD_STATUS` 卡状态定义。
* [交易生命周期](../basic-concepts/transaction-lifecycle)：理解 `CARD_TRANSACTION` 与 `CARD_TRANSACTION_SETTLEMENT` 的授权/清算两段式。
