> ## 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 Real-time Notifications

> DCS keeps you in sync in real time with key events such as KYC status, asset movements, card transactions and order status, over two complementary channels: Webhook, where DCS pushes to your callback URL, and WebSocket, where you subscribe to a private channel and can replay by version. Both channels share the same nine business event types and the same data structures; only the message envelope differs in field names and types. WebSocket is a DeCard-Managed differentiator.

Whether you are an exchange, a wallet or a platform, DCS tells you over a real-time channel whenever a user's KYC, balance, card or order changes, so you never have to poll. This page gets both channels working in one pass:

* **Webhook**: push based. You expose an HTTPS callback endpoint and DCS pushes signed JSON to it as events occur. Best suited to stable server-to-server delivery.
* **WebSocket**: subscription based, and a DeCard-Managed differentiator. You first obtain a dedicated private channel, then hold a `wss` connection open to receive messages live; if you miss anything, you can replay history by `version`. Best suited to cases that need front-end or gateway awareness in real time, or that require a replay path for reconciliation.

<Warning>
  Privacy red line: the DeCard-Managed model handles a great deal of end-user personal data. Every `externalUserId`, card number, merchant name, address, `txHash` and `secretKey` in the examples on this page is a **placeholder or masked value** and must not be treated as real data. Mask the same fields in your own receiver logs, and never record real PII or keys in clear text.
</Warning>

***

## 1. Webhook (push based)

### 1.1 Core capabilities

* **Automatic retries**: when delivery fails, DCS retries automatically, **up to 3 times**.
* **Signature verification**: every notification carries an `X-Signature` digital signature in the request header so you can trust its origin.
* **Idempotency**: every event carries a globally unique `webhookId` that you can deduplicate on.

### 1.2 Prerequisites and integration steps

#### Step 1: Implement the Webhook receiver

Your service must expose a `POST` endpoint that meets the following requirements:

* Accepts an `application/json` request body;
* Returns `200 OK` to acknowledge receipt. **Any other status code** makes DCS retry;
* **Must respond within 2 seconds** (both the connect timeout and the socket timeout on the DCS side are 2000 ms), otherwise the delivery counts as a timeout failure and is likewise retried.

#### Step 2: Give DCS the callback URL to configure

Send the callback URL you implemented in step 1 to the DCS team. It **must be HTTPS and reachable from the public internet**.

#### Step 3: Verify, then go live

Verify in the UAT (sandbox) environment first, and go live in production only once verification passes.

### 1.3 Delivery sequence and retries

<Frame>
  <img className="block dark:hidden" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/en/diagrams/va-webhook-retry-light.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=dbde3557720f1f0616678e653c42eb02" alt="Webhook delivery sequence and retries" width="560" height="644" data-path="imgs/en/diagrams/va-webhook-retry-light.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/en/diagrams/va-webhook-retry-dark.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=876e3edf4f5dcdcf011d94ccf42133a5" alt="Webhook delivery sequence and retries" width="560" height="644" data-path="imgs/en/diagrams/va-webhook-retry-dark.svg" />
</Frame>

### 1.4 Security: `X-Signature` verification

To keep the channel secure, DCS carries `X-Signature` in the headers of every Webhook request. The signature is produced with **HmacSHA256**: DCS computes it over the **raw body** using your `SecretKey`. On receipt, recompute the signature over the raw body with the same `SecretKey` and compare it with `X-Signature` to confirm that the request is authentic and intact.

<Warning>
  The signature header is always `X-Signature`, and the HMAC key is your `SecretKey`, not the `apiKey`. Implement verification accordingly.
</Warning>

#### Headers

| Header         | Description                                                                 |
| :------------- | :-------------------------------------------------------------------------- |
| `X-Signature`  | The signature string (HmacSHA256 over the raw body, keyed with `SecretKey`) |
| `Content-Type` | Always `application/json`                                                   |

#### Signature calculation reference (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>";              // Placeholder; read it from your key management service
        String webhookPayloadStr = "<raw-webhook-body-string>"; // The raw JSON string; never deserialize and re-serialize it
        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));        // Compare this with X-Signature
    }
}
```

#### Receiver reference (Java, with the 7-step processing convention)

```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";
    // Illustrative only; read this from your key management service
    private static final String apiSecret = "<your-secret-key>";

    @PostMapping("/your-path/webhook")
    public ResponseEntity<String> processWebhook(
            @RequestHeader("X-Signature") String signature,
            @RequestBody String rawBody // Must be received as a String; never bind straight to an object!
    ) {
        // 1. Verify the signature (recompute over the raw body and compare with X-Signature)
        if (!verifySignature(rawBody, signature)) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid signature");
        }
        // 2. Parse rawBody to read webhookId, type and the other envelope fields
        // 3. If this type is not one you care about, ignore it and return 200 straight away
        // 4. Idempotency: check whether this webhookId has already been processed
        // 5. Parse the business data (optionally into a typed object)
        // 6. Run the business logic (asynchronously, so you do not exceed the 2-second timeout)
        // 7. Mark the webhookId as processed (idempotency)
        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 common envelope

<Warning>
  Watch the differences in field names and types: Webhook uses `notificationTimestamp` / `eventTimestamp` (**`Long`, in milliseconds**), amount and quantity fields such as `freeDelta` and `transactionAmount` are `BigDecimal`, and `tranId` is `Long`. **The WebSocket equivalents are all `string`, and its time field is named `timestamp`** (see [section 2.4](#2-4-websocket-common-envelope)), so do not mix the types up when reading the two side by side.
</Warning>

| Name                    | Type   | Description                                                                                                     |
| :---------------------- | :----- | :-------------------------------------------------------------------------------------------------------------- |
| `webhookId`             | string | Globally unique webhook ID, usable for idempotent deduplication                                                 |
| `type`                  | string | Notification type (see [the event catalogue in section 3](#3-business-event-catalogue-shared-by-both-channels)) |
| `externalUserId`        | string | External user ID                                                                                                |
| `notificationTimestamp` | Long   | Time the notification was sent, Unix timestamp in milliseconds                                                  |
| `eventTimestamp`        | Long   | Time the event was recorded, Unix timestamp in milliseconds                                                     |
| `data`                  | object | The business payload of the event; its structure depends on `type`                                              |

**Webhook notification example (asset movement, masked):**

```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 (subscription based, ★ a DeCard-Managed differentiator)

> The WebSocket channel is unique to the DeCard-Managed model: you can subscribe to a dedicated private channel to receive messages live, and replay anything you missed by `version`.

Real-time push over WebSocket keeps user-related changes in sync, covering KYC status, asset movements, card transactions, order status and other key information, so you can react to user activity faster.

### 2.1 Prerequisites and three-step integration

1. **Create a listening channel**: call `GET /websocket/v1/get-channel` to generate a dedicated private channel for your organization. The channel string is the unique identifier used to receive messages, for example `HBHmMK99SJEVMVUqCb4`.
2. **Open the `wss` connection**: substitute the channel string for `{channel}` in the `wss` address, pick mainnet or testnet according to your environment (see [section 2.3](#2-3-websocket-environment-endpoints)), open the connection and listen for messages.
3. **Rotate channels and replay gaps**: each channel **lives for 24 hours** and then expires. To avoid losing messages, call `get-channel` again before it expires; calling it an hour ahead of expiry gives the old and new channels an overlap window wide enough for a clean switchover. If you miss messages or need to replay, use `GET /websocket/v1/search` to look up history by `version`.

### 2.2 Channel lifecycle

<Frame>
  <img className="block dark:hidden" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/en/diagrams/va-ws-lifecycle-light.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=260752fa034bb76d2c59c94229182c6d" alt="WebSocket channel lifecycle" width="741" height="480" data-path="imgs/en/diagrams/va-ws-lifecycle-light.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/en/diagrams/va-ws-lifecycle-dark.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=6b367d134869cf6535daceb81fb93d28" alt="WebSocket channel lifecycle" width="741" height="480" data-path="imgs/en/diagrams/va-ws-lifecycle-dark.svg" />
</Frame>

<Warning>
  Channel security: **once a new channel is generated, the old channel stops pushing data**. Time the switchover correctly so you are not left waiting on a dead channel.
</Warning>

### 2.3 WebSocket environment endpoints

| Environment          | URL                                       |
| :------------------- | :---------------------------------------- |
| Mainnet (production) | `wss://stream.thedecard.com/ws/{channel}` |
| Test (sandbox)       | `wss://stream.uatdcd.com/ws/{channel}`    |

> Replace `{channel}` with the channel string returned by `get-channel`.

### 2.4 WebSocket common envelope

<Warning>
  How it differs from the Webhook envelope: WebSocket uses **`timestamp` (a `string`)** and carries **an extra `version` field**, and every business field inside `data` (amounts, quantities, `tranId` and so on) is a **`string`**. That differs from the `Long` and `BigDecimal` types used by Webhook, so treat this table as authoritative.
</Warning>

| Name             | Type   | Description                                                                                                |
| :--------------- | :----- | :--------------------------------------------------------------------------------------------------------- |
| `type`           | string | Message type (see [the event catalogue in section 3](#3-business-event-catalogue-shared-by-both-channels)) |
| `timestamp`      | string | Message timestamp                                                                                          |
| `version`        | string | Message version number, usable with `GET /websocket/v1/search` to replay history                           |
| `externalUserId` | string | User ID                                                                                                    |
| `data`           | object | The business payload of the event; its structure depends on `type`                                         |

**WebSocket message example (asset movement, masked):**

```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 endpoint reference

Both endpoints return the site-wide common response envelope:

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

| Field           | Description                                                                                                                |
| :-------------- | :------------------------------------------------------------------------------------------------------------------------- |
| `code`          | Business status code; `SYS_SUCCESS` on success (identical in both integration models), or a specific error code on failure |
| `message`       | Short message, usually `null` on success                                                                                   |
| `messageDetail` | Detail object, whose sub-fields are usually empty                                                                          |
| `data`          | string. `get-channel` returns the channel identifier string; `search` returns the historical messages found                |

> Decide success from `code == "SYS_SUCCESS"`; the response envelope **contains no** `success` boolean field.

#### Obtain a listening channel

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

No request parameters. `data` returns the private channel identifier string that belongs to your organization.

#### Look up historical messages by version

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

| Parameter | In    | Required | Description                                                                                                                                                                                                     |
| :-------- | :---- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `version` | query | REQUIRED | Message version number; returns the historical message for that version, so you can replay what you missed. The value is the `version` field from the real-time WebSocket push (see the example in section 2.4) |

> These two REST endpoints behave like every other REST endpoint on the site, so carry the signature headers (`X-DAPI-API-KEY` / `X-DAPI-TIMESTAMP` / `X-DAPI-NONCE` / `X-DAPI-SIGN`) as described in the [Authentication Guide](./overview).

***

## 3. Business event catalogue (shared by both channels)

Both channels **share the same business event types and the same `data` structures**, eight in total. Only the envelope differs: Webhook uses `notificationTimestamp` / `eventTimestamp` (`Long`) while WebSocket uses `timestamp` plus `version` (`string`), and the field types inside `data` differ as described above (Webhook `BigDecimal`/`Long` ↔ WebSocket `string`).

| `type`                        | Meaning                            | Key `data` fields / state machine                                                                                                                  |
| :---------------------------- | :--------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BALANCE_CHANGE`              | Digital asset movement             | `asset` / `freeDelta` / `freezeDelta` / `free` / `freeze` / `tranId` / `externalTranId` / `type`                                                   |
| `CARD_TRANSACTION`            | Card transaction record            | See [3.2](#3-2-card_transaction-card-transactions); includes `direction`, `transactionType` and `originalExternalTranId` (reversal scenarios only) |
| `CARD_TRANSACTION_SETTLEMENT` | Card settlement record             | `settledAmount` / `settledCurrencyCode` / `transactionType` and others                                                                             |
| `CARD_PHYSICAL_SHIPPING`      | Physical card shipping information | `status` (pending embossing / embossing / in delivery / delivered) / `trackingNumber` / `trackingCompanyName`                                      |
| `ORDER_STATUS`                | FOMO top-up (order) status         | `txHash` / `transStatus` (5 values) / `asset` / `creditCurrency` / `convertCurrency` and others                                                    |
| `QR_ORDER_STATUS`             | QR Pay order status                | `orderId` / `orderStatus` (7 values)                                                                                                               |
| `CARD_APPLY`                  | Card application status            | `applyId` / `status` (PENDING/SUCCEED/FAILED) / `needExtraInfo`                                                                                    |
| `CARD_STATUS`                 | Card status change                 | `cardId` / `cardMantissa` / `cardStatus` (NORMAL/FROZEN/CANCELLED)                                                                                 |

> Terminology: the terminal `SUCCEED` of `CARD_APPLY` is a **card application** status, spelled `SUCCEED` rather than `SUCCESS`, and it follows different conventions from the card order and card state machines, so do not confuse them. The `NORMAL/FROZEN/CANCELLED` values of `CARD_STATUS` are aligned with the card state machine in [Card Management](../how-to-use/managing-cards/overview).

### 3.1 `BALANCE_CHANGE` digital asset movement

| Name             | Type (WS / Webhook) | Description                    |
| :--------------- | :------------------ | :----------------------------- |
| `tranId`         | string / Long       | Transaction reference          |
| `externalTranId` | string              | External transaction ID        |
| `asset`          | string              | Asset currency                 |
| `network`        | string              | Network                        |
| `freeDelta`      | string / BigDecimal | Change in asset balance        |
| `freezeDelta`    | string / BigDecimal | Change in frozen asset balance |
| `free`           | string / BigDecimal | Available holding              |
| `freeze`         | string / BigDecimal | Frozen assets                  |
| `type`           | string              | Type of movement               |

### 3.2 `CARD_TRANSACTION` card transactions

| Name                       | Type (WS / Webhook) | Description                                                                                                                                                                      |
| :------------------------- | :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cardNumber`               | string              | Last 4 digits of the card number                                                                                                                                                 |
| `transactionCurrencyCode`  | string              | Transaction currency, ISO code (such as `840` or `702`)                                                                                                                          |
| `transactionAmount`        | string / BigDecimal | Transaction amount                                                                                                                                                               |
| `localTransactionDate`     | string              | Transaction date (such as `1211`)                                                                                                                                                |
| `localTransactionTime`     | string              | Transaction time (such as `161420`)                                                                                                                                              |
| `response`                 | string              | Transaction outcome (A=accept/success, D=deny/fail)                                                                                                                              |
| `externalTranId`           | string / Long       | External transaction ID                                                                                                                                                          |
| `systemTraceAuditNumber`   | string              | System trace audit number                                                                                                                                                        |
| `requestAmountInUsd`       | string              | Transaction amount in USD                                                                                                                                                        |
| `mcc`                      | string              | Merchant category code                                                                                                                                                           |
| `cardAcceptorNameLocation` | string              | Merchant name and location                                                                                                                                                       |
| `direction`                | string              | Direction of funds: `DEBIT` (spend), `CREDIT` (credit or refund)                                                                                                                 |
| `settlementCurrencyCode`   | string              | Posting currency of the card account (ISO 4217)                                                                                                                                  |
| `settlementAmount`         | string / BigDecimal | Posting amount on the card account                                                                                                                                               |
| `transactionType`          | string              | Transaction type: R=card purchase, C=ATM withdrawal, Q=enquiry, P=transfer or refund                                                                                             |
| `merchantCountryCode`      | string              | Merchant country code (3 digits)                                                                                                                                                 |
| `originalExternalTranId`   | string              | External transaction ID of the original transaction, **returned only in reversal scenarios** such as a void, reversal or refund; ordinary authorizations do not carry this field |

> Today `CARD_TRANSACTION` distinguishes approval from decline only through `response=A/D`, and carries no finer-grained decline reason code; there is no `declinedReason` or `declineCode` field on the transaction query side either. Breaking the reasons down would require system changes in DCS and txn-auth. For now, work through balance, card status and risk or MCC rules one at a time.

**Example (refund, with `originalExternalTranId`, masked):**

```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 settlement records

The fields are broadly the same as `CARD_TRANSACTION`, with two differences: the `settlement*` fields are replaced by `settledAmount` (settled amount) and `settledCurrencyCode` (settlement currency, ISO 4217); and only `direction` is present, without `response` or the other authorization-specific fields such as `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` physical card shipping information

| Name                  | Type   | Description                                                                                                                                                       |
| :-------------------- | :----- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cardMantissa`        | string | Last 4 digits of the card number                                                                                                                                  |
| `status`              | string | Shipping status: `PENDING_EMBOSSING` (awaiting embossing), `EMBOSSING_IN_PROGRESS` (being embossed), `IN_DELIVERY` (in delivery), `DELIVERY_COMPLETE` (delivered) |
| `trackingNumber`      | string | Tracking number (provided only in `IN_DELIVERY` / `DELIVERY_COMPLETE` status)                                                                                     |
| `trackingCompanyName` | string | Courier name (provided only in `IN_DELIVERY` / `DELIVERY_COMPLETE` status)                                                                                        |

### 3.5 `ORDER_STATUS` FOMO top-up status

| Name                 | Type (WS / Webhook) | Description                                                               |
| :------------------- | :------------------ | :------------------------------------------------------------------------ |
| `txHash`             | string              | On-chain transaction hash (may be empty for orders that are not on-chain) |
| `transStatus`        | string              | Order status, see the enum below                                          |
| `asset`              | string              | Original asset currency (USDC / USDT)                                     |
| `amount`             | string / BigDecimal | Original transaction amount                                               |
| `network`            | string              | Blockchain network (empty for fiat or internal orders)                    |
| `fromAddress`        | string              | Sending address                                                           |
| `toAddress`          | string              | Receiving address                                                         |
| `creditCurrency`     | string              | Currency actually credited (SGD / USD / USDC / USDT)                      |
| `creditAmount`       | string / BigDecimal | Amount actually credited                                                  |
| `convertCurrency`    | string              | Currency after conversion (SGD / USD)                                     |
| `convertAmount`      | string / BigDecimal | Amount after conversion                                                   |
| `convertFeeCurrency` | string              | Conversion fee currency (SGD / USD)                                       |
| `convertFeeAmount`   | string / BigDecimal | Conversion fee amount                                                     |

**`transStatus` enum:**

| Value            | Terminal | Description                                           |
| :--------------- | :------- | :---------------------------------------------------- |
| `PENDING_NORMAL` | No       | Order created, waiting to be processed                |
| `PENDING_CREDIT` | No       | External transaction complete, waiting to be credited |
| `PENDING_AUDIT`  | No       | Order under review                                    |
| `SUCCESS`        | Yes      | Order processed successfully                          |
| `FAILED`         | Yes      | Order processing failed                               |

### 3.6 `QR_ORDER_STATUS` QR Pay order status

| Name          | Type (WS / Webhook) | Description                      |
| :------------ | :------------------ | :------------------------------- |
| `orderId`     | string / Long       | Order ID                         |
| `orderStatus` | string              | Order status, see the enum below |

**`orderStatus` enum:**

| Value                  | Terminal | Description                          |
| :--------------------- | :------- | :----------------------------------- |
| `PENDING`              | No       | Awaiting payment                     |
| `SUCCESS`              | Yes      | Order processed successfully         |
| `FAILED`               | Yes      | Order processing failed              |
| `FULL_REFUNDED`        | Yes      | Fully refunded                       |
| `PARTIAL_REFUNDED`     | Yes      | Partially refunded                   |
| `PENDING_CONFIRMATION` | No       | Payment result awaiting confirmation |
| `PROCESSING`           | No       | Payment in progress                  |

### 3.7 `CARD_APPLY` card application status

| Name            | Type (WS / Webhook) | Description                                                                |
| :-------------- | :------------------ | :------------------------------------------------------------------------- |
| `applyId`       | string              | Application ID                                                             |
| `categoryId`    | string / Long       | Card category ID                                                           |
| `network`       | string              | Card network (such as VISA or MASTERCARD)                                  |
| `currency`      | string              | Currency                                                                   |
| `applyRef`      | string              | Idempotency key of the application                                         |
| `status`        | string              | Application status, see the enum below                                     |
| `errorCode`     | string              | Error code for a failed application (provided only in `FAILED` status)     |
| `needExtraInfo` | boolean             | Whether the user has to supply additional information, see the notes below |

**`status` enum:**

| Value     | Terminal | Description                                                                                   |
| :-------- | :------- | :-------------------------------------------------------------------------------------------- |
| `PENDING` | No       | Application in progress                                                                       |
| `SUCCEED` | Yes      | Succeeded (the terminal state of a card application, spelled `SUCCEED` rather than `SUCCESS`) |
| `FAILED`  | Yes      | Failed                                                                                        |

**Notes on `needExtraInfo`:**

| Value   | Meaning                                                                                                                                             |
| :------ | :-------------------------------------------------------------------------------------------------------------------------------------------------- |
| `true`  | The user has not yet submitted the additional information; guide them through the upload (use the hosted guidance page with `action=KYC_EXTRA_DOC`) |
| `false` | The user has completed the submission. Even if `status` is still `PENDING`, wait for our review rather than prompting the user again                |

### 3.8 `CARD_STATUS` card status

Notifies you of the latest card status whenever it changes. Virtual card status (`cardStatus`) is supported today; physical card status (`physicalCardStatus`) will follow.

| Name           | Type   | Description                             |
| :------------- | :----- | :-------------------------------------- |
| `cardId`       | string | Card ID                                 |
| `cardMantissa` | string | Last 4 digits of the card number        |
| `cardStatus`   | string | Virtual card status, see the enum below |

**`cardStatus` enum (aligned with the [card management state machine](../how-to-use/managing-cards/overview)):**

| Value       | Description |
| :---------- | :---------- |
| `NORMAL`    | Normal      |
| `FROZEN`    | Frozen      |
| `CANCELLED` | Closed      |

***

## 4. Best practices and common questions

* **Verify first, process second**: before you touch any payload, recompute HmacSHA256 over the raw body with your `SecretKey` and compare it with `X-Signature`. If verification fails, reject the request with a 401, raise an alert, and process nothing.
* **Return 200 fast, do the work asynchronously**: DCS expects a response **within 2 seconds**. Persist the notification with its `webhookId`, return `200` immediately, and then process the business logic asynchronously so you never exceed the timeout and trigger needless retries.
* **Deduplicate on `webhookId`**: retries mean the same notification can arrive more than once. Store the `webhookId` values you have processed and check for duplicates before processing.
* **Mind the ordering**: network jitter and retries can make Webhooks arrive out of order. If you need to process events in the order they actually happened, sort by `eventTimestamp` (the time the event was recorded, see section 1.5) rather than by arrival time, and add a short buffering window such as 30 seconds if necessary before processing in batches.
* **Handling event types you do not care about**: for event types you have no use for, simply return `200` and ignore them. Do not return an error code, because error codes trigger retries.
* **Rotate WebSocket channels early**: a channel lives only 24 hours, and the old channel stops pushing as soon as a new one is generated, so call `get-channel` before expiry and switch over smoothly.
* **Use `version` as your safety net**: a WebSocket connection can drop messages when the network is unstable, so reconcile regularly and backfill with `GET /websocket/v1/search?version=`.
* **Choose the channels you need**: if delivery stability matters most, start with Webhook; if you need real-time responsiveness, front-end awareness or replay, add WebSocket. The event semantics are identical on both.
* **No sensitive card data over the real-time channels**: notifications carry only the masked last 4 digits of the card number, never the full PAN or the CVV. To obtain sensitive card data, use the dedicated encrypted API (see [Viewing Encrypted Card Details](../how-to-use/managing-cards/viewing-encrypted-card-details)).

***

## Next steps / Related

* [Authentication Guide](./overview): the two WebSocket REST endpoints behave like every other REST endpoint on the site, so carry the signature headers as described there.
* [Card Management](../how-to-use/managing-cards/overview): the conventions of the `CARD_STATUS` card state machine.
* [Transaction Lifecycle](../basic-concepts/transaction-lifecycle): understanding the two stages, authorization and settlement, behind `CARD_TRANSACTION` and `CARD_TRANSACTION_SETTLEMENT`.
