> ## 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.

# Events and data structures

> The envelope fields, businessId mapping and per-event data structures for all 18 corporate card outbound webhook events; configuration and signature verification are covered in Webhook configuration.

## 📄 Guide

Organization onboarding, employee creation, card issuing, card status, 3DS challenges, the three transaction events and the deposit alert add up to **18** outbound events in five groups. Every event shares one envelope — read `webhookType`, then parse the matching `data`. Your receiver needs a single endpoint.

> **Prerequisites**: register the callback URL and be ready to recompute `X-Signature` with your `SK`. Setup steps and verification code are in [Webhook configuration](./configuration).

<Frame>
  <img className="block dark:hidden" src="https://mintcdn.com/dcs-0bf7a937/PxXwc3LMXUdQj7w7/imgs/en/diagrams/corp-webhook-dispatch-light.svg?fit=max&auto=format&n=PxXwc3LMXUdQj7w7&q=85&s=e9f70254f819fb52f98061721290640c" alt="Webhook verification and dispatch by event type" width="769" height="396" data-path="imgs/en/diagrams/corp-webhook-dispatch-light.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/dcs-0bf7a937/PxXwc3LMXUdQj7w7/imgs/en/diagrams/corp-webhook-dispatch-dark.svg?fit=max&auto=format&n=PxXwc3LMXUdQj7w7&q=85&s=4c0b152e2757ad322c755ef3330b2ff6" alt="Webhook verification and dispatch by event type" width="769" height="396" data-path="imgs/en/diagrams/corp-webhook-dispatch-dark.svg" />
</Frame>

## The envelope

```json theme={null}
{
  "webhookId": "7800000000000000900",
  "webhookType": "ORGANIZATION_CREATED",
  "businessId": "7810000000000000123",
  "data": { },
  "notificationTime": "2026-08-25T02:30:00Z"
}
```

| Field              | Type   | Description                                                                                                           |
| :----------------- | :----- | :-------------------------------------------------------------------------------------------------------------------- |
| `webhookId`        | String | The webhook's unique ID. **The idempotency key** — reused across every retry of the same event                        |
| `webhookType`      | String | Event type code, uppercase with underscores; the full set is below                                                    |
| `businessId`       | String | The business ID this delivery is about. **Which kind of ID it is depends on `webhookType`** — see the mapping below   |
| `data`             | Object | Business data; the fields vary by `webhookType`                                                                       |
| `notificationTime` | String | Delivery timestamp, ISO-8601 UTC. **It is the time of this delivery, so retries of one event carry different values** |

<Note>
  The envelope matches the common webhook structure used by the platform's other product lines, so one consumer can handle events from all of them. Every ID field is a string; amounts are strings and trailing zeros are not guaranteed.
</Note>

## Event overview (18)

| Group        | `webhookType`                 | `businessId` is       | Trigger                                                                                             |
| :----------- | :---------------------------- | :-------------------- | :-------------------------------------------------------------------------------------------------- |
| Organization | `ORGANIZATION_CREATED`        | `organizationApplyId` | KYB passed, organization created                                                                    |
|              | `ORGANIZATION_REJECTED`       | `organizationApplyId` | KYB rejected                                                                                        |
|              | `ORGANIZATION_STATUS_CHANGED` | `organizationId`      | Organization restrictions changed (added / removed)                                                 |
| Employee     | `CUSTOMER_CREATED`            | `customerApplyId`     | KYC passed, employee created                                                                        |
|              | `CUSTOMER_REJECTED`           | `customerApplyId`     | KYC rejected                                                                                        |
|              | `CUSTOMER_STATUS_CHANGED`     | `customerId`          | Employee restrictions changed (added / removed)                                                     |
| Card         | `CARD_CREATED`                | `cardApplyId`         | Risk checks passed and the card was created                                                         |
|              | `CARD_REJECTED`               | `cardApplyId`         | Risk declined, or card creation failed                                                              |
|              | `CARD_SHIPPED`                | `cardId`              | The physical card was produced and dispatched (once, when the tracking number is generated)         |
|              | `CARD_ACTIVATED`              | `cardId`              | The physical card was activated                                                                     |
|              | `CARD_STATUS_CHANGED`         | `cardId`              | Card status changed (freeze / unfreeze / lock / unlock / lost / close); virtual and physical alike  |
|              | `AUTHORISATION_3DS_CHALLENGE` | `challengeId`         | A cardholder transaction triggered a 3DS challenge needing your verification or OTP delivery        |
| Transaction  | `CARD_TRANSACTION`            | `transactionId`       | Card authorization / release (approval and decline share one event, distinguished by `data.status`) |
|              | `CARD_TRANSACTION_SETTLEMENT` | `transactionId`       | Card settlement                                                                                     |
|              | `CARD_TRANSACTION_DEBT`       | `transactionId`       | The card entered a debt state                                                                       |
| Funds        | `BANK_TRANSFER_INFO`          | `depositId`           | A VA deposit landed (both success and failure, distinguished by `data.status`)                      |
|              | `BALANCE_CHANGE`              | `transactionId`       | Balance changed                                                                                     |
|              | `LOW_BALANCE`                 | `organizationId`      | Funding pool balance below the alert threshold                                                      |

<Note>
  Every event's `data` carries `organizationId` except `ORGANIZATION_REJECTED` (the entity does not exist yet) and the three transaction events (attribution is derivable from `cardId`), so you never have to look up ownership.
</Note>

***

## Organization events

### `ORGANIZATION_CREATED`

| Field                 | Type   | Description                                       |
| :-------------------- | :----- | :------------------------------------------------ |
| `organizationId`      | String | Organization ID                                   |
| `organizationType`    | String | Organization type; currently only `COMPANY`       |
| `organizationRef`     | String | Your own organization identifier (echoed)         |
| `organizationApplyId` | String | Organization application ID                       |
| `status`              | String | The organization entity's status, always `ACTIVE` |

### `ORGANIZATION_REJECTED`

| Field                 | Type   | Description                               |
| :-------------------- | :----- | :---------------------------------------- |
| `organizationType`    | String | Organization type                         |
| `organizationRef`     | String | Your own organization identifier          |
| `organizationApplyId` | String | Organization application ID               |
| `status`              | String | The application status, always `REJECTED` |
| `rejectMessage`       | String | The KYB rejection reason                  |

<Warning>
  This event **carries no `organizationId`** — the organization entity is only persisted once KYB passes, so it does not exist at rejection time. Match on `organizationRef` or `organizationApplyId`. Also note that `status` here refers to the **application**, not the entity as in `ORGANIZATION_CREATED`.
</Warning>

### `ORGANIZATION_STATUS_CHANGED`

Mirrors [updating organization restrictions](../company-maintenance); pushed once the change is committed.

| Field                | Type           | Description                         |
| :------------------- | :------------- | :---------------------------------- |
| `organizationId`     | String         | Organization ID                     |
| `organizationType`   | String         | Organization type                   |
| `addRestrictions`    | Array\<String> | Restrictions added by this change   |
| `removeRestrictions` | Array\<String> | Restrictions removed by this change |
| `remark`             | String         | Operation remark                    |

***

## Employee events

### `CUSTOMER_CREATED`

| Field             | Type   | Description                                   |
| :---------------- | :----- | :-------------------------------------------- |
| `customerId`      | String | Employee ID                                   |
| `customerRef`     | String | Your own employee identifier (echoed)         |
| `organizationId`  | String | The organization the employee belongs to      |
| `customerApplyId` | String | Employee application ID                       |
| `status`          | String | The employee entity's status, always `ACTIVE` |

### `CUSTOMER_REJECTED`

| Field             | Type   | Description                               |
| :---------------- | :----- | :---------------------------------------- |
| `customerRef`     | String | Your own employee identifier              |
| `organizationId`  | String | The organization the employee belongs to  |
| `customerApplyId` | String | Employee application ID                   |
| `status`          | String | The application status, always `REJECTED` |
| `rejectMessage`   | String | The KYC rejection reason                  |

### `CUSTOMER_STATUS_CHANGED`

Mirrors [updating employee restrictions](../employee-maintenance).

| Field                | Type           | Description                              |
| :------------------- | :------------- | :--------------------------------------- |
| `customerId`         | String         | Employee ID                              |
| `organizationId`     | String         | The organization the employee belongs to |
| `addRestrictions`    | Array\<String> | Restrictions added by this change        |
| `removeRestrictions` | Array\<String> | Restrictions removed by this change      |
| `remark`             | String         | Operation remark                         |

***

## Card events

### `CARD_CREATED`

| Field            | Type   | Description                              |
| :--------------- | :----- | :--------------------------------------- |
| `cardId`         | String | Card ID                                  |
| `organizationId` | String | The organization the card belongs to     |
| `cardApplyId`    | String | Card application ID                      |
| `cardApplyRef`   | String | Your own application identifier (echoed) |
| `status`         | String | Card status, always `ACTIVE`             |

### `CARD_REJECTED`

| Field            | Type   | Description                                           |
| :--------------- | :----- | :---------------------------------------------------- |
| `organizationId` | String | The organization the card belongs to                  |
| `cardApplyId`    | String | Card application ID                                   |
| `cardApplyRef`   | String | Your own application identifier (echoed)              |
| `status`         | String | The application status, always `REJECTED`             |
| `rejectMessage`  | String | Why it failed (risk decline or a card-creation error) |

### `CARD_SHIPPED`

Pushed when the tracking number is generated, **exactly once**.

| Field                 | Type   | Description                          |
| :-------------------- | :----- | :----------------------------------- |
| `cardId`              | String | Card ID                              |
| `organizationId`      | String | The organization the card belongs to |
| `trackingNumber`      | String | Tracking number                      |
| `trackingCompanyName` | String | Courier                              |
| `trackingNumberDate`  | String | Waybill date, `yyyy-MM-dd`           |

### `CARD_ACTIVATED`

| Field            | Type   | Description                          |
| :--------------- | :----- | :----------------------------------- |
| `cardId`         | String | Card ID                              |
| `organizationId` | String | The organization the card belongs to |

### `CARD_STATUS_CHANGED`

| Field            | Type   | Description                              |
| :--------------- | :----- | :--------------------------------------- |
| `cardId`         | String | Card ID                                  |
| `organizationId` | String | The organization the card belongs to     |
| `fromStatus`     | String | Card status before the change (one of 7) |
| `toStatus`       | String | Card status after the change (one of 7)  |

<Note>
  This event covers neither **activation** nor **expiry**: activation is pushed as `CARD_ACTIVATED`, and expiry currently has no trigger. The 7 card statuses are listed in [Managing cards](../managing-cards).
</Note>

### `AUTHORISATION_3DS_CHALLENGE`

The business flow and how to reply are covered in [3DS challenges](../3ds-challenges).

| Field                 | Type   | Description                                                                    |
| :-------------------- | :----- | :----------------------------------------------------------------------------- |
| `challengeId`         | String | Challenge ID, echoed back when you reply. **Deduplicate on it**                |
| `status`              | String | Always `INIT` — the challenge's final state is not notified through this event |
| `cardId`              | String | The card that triggered the challenge                                          |
| `organizationId`      | String | The organization the card belongs to                                           |
| `expiryTime`          | String | When the challenge expires, ISO-8601 UTC (about 300 seconds by default)        |
| `currency`            | String | Transaction currency, ISO-4217; paired with `amount`                           |
| `amount`              | String | Transaction amount                                                             |
| `merchantId`          | String | Merchant identifier                                                            |
| `merchantName`        | String | Merchant name                                                                  |
| `merchantCountryCode` | String | Merchant country, ISO-3166-1 alpha-2 uppercase                                 |
| `mcc`                 | String | Merchant category code, four digits                                            |
| `challengeFlowType`   | String | `OOB` / `OTP_DELEGATE` — tells you whether to parse the encrypted fields below |
| `challengeMethodType` | String | `DELEGATE_SCA_V1` / `SMS_OTP` / `EMAIL_OTP`                                    |

The following four fields are populated **only when `challengeFlowType=OTP_DELEGATE` and the partner is configured with `otpSendMode=ENTERPRISE`**:

| Field                  | Type   | Description                                                                        |
| :--------------------- | :----- | :--------------------------------------------------------------------------------- |
| `encryptedOtpPasscode` | String | The OTP, encrypted                                                                 |
| `encryptedPhoneNumber` | String | The cardholder's mobile number, encrypted                                          |
| `encryptedEmail`       | String | The cardholder's email address, encrypted                                          |
| `iv`                   | String | AES-GCM initialisation vector (Base64), shared by the three encrypted fields above |

Decryption: `AES/GCM/NoPadding` with a 128-bit authentication tag; the key is your `SK` (the same one used for request signing), following the same contract as retrieving secure card details.

<Warning>
  `merchantCountryCode` changes from alpha-3 (three letters) to **alpha-2 (two uppercase letters)** — that is a value conversion, not just a rename, so your parsing needs to change with it.
</Warning>

***

## Transaction events

The three transaction events describe the same facts as statement line items and reuse the statement domain's vocabulary: `panLast4` / `transactionTime` / `transactionCategory` / `originalAmount`+`originalCurrency` (the original transaction amount) / `postAmount`+`postCurrency` (the posted amount) / `merchantName` / `mcc` / `merchantCountryCode`.

<Note>
  **Transaction events carry no `organizationId`**: attribution is derivable from `cardId` — a card's organization is fixed at issue time and never changes, and you already received it in `CARD_CREATED`. Transaction events are far higher in volume than lifecycle events, so they carry only what is necessary. To aggregate by organization, use [Statements and transactions](../statements-and-transactions).
</Note>

### `CARD_TRANSACTION`

Authorization and release share one event; approval versus decline is given by `status`.

| Field                                 | Type   | Description                                                                                      |
| :------------------------------------ | :----- | :----------------------------------------------------------------------------------------------- |
| `transactionId`                       | String | Transaction ID, identical to the statement line's, so it reconciles directly                     |
| `originalTransactionId`               | String | The original transaction's ID. Present only for release / reversal / refund events               |
| `cardId`                              | String | Card ID                                                                                          |
| `panLast4`                            | String | Last four digits of the card number                                                              |
| `status`                              | String | Authorization result: `APPROVED` / `DECLINED`                                                    |
| `direction`                           | String | Debit or credit: `DEBIT` / `CREDIT` / `DEDUCT`. Absent entirely when upstream sends no indicator |
| `originalAmount` / `originalCurrency` | String | The original transaction amount and currency                                                     |
| `postAmount` / `postCurrency`         | String | The posted (accounting) amount and currency                                                      |
| `transactionTime`                     | String | Transaction time in the acquirer's local time, `yyyy-MM-dd HH:mm:ss`                             |
| `transactionCategory`                 | String | `SALES` / `CASH_ADVANCE` / `PAYMENT` / `INSTALLMENT` / `INQUIRY` / `TRANSFER_OUT`                |
| `merchantName`                        | String | Merchant name, passed through from upstream and may include an address                           |
| `merchantCountryCode`                 | String | Merchant country, alpha-2 uppercase                                                              |
| `mcc`                                 | String | Merchant category code, four digits                                                              |

```json theme={null}
{
  "webhookId": "7800000000000001201",
  "webhookType": "CARD_TRANSACTION",
  "businessId": "5185740066240791001",
  "notificationTime": "2026-08-25T02:30:00Z",
  "data": {
    "transactionId": "5185740066240791001",
    "cardId": "5136759791164443137",
    "panLast4": "4821",
    "status": "APPROVED",
    "direction": "DEBIT",
    "originalAmount": "128.50",
    "originalCurrency": "HKD",
    "postAmount": "16.47",
    "postCurrency": "USD",
    "transactionTime": "2026-08-25 10:30:00",
    "transactionCategory": "SALES",
    "merchantName": "STARBUCKS TSIM SHA TSUI HK",
    "merchantCountryCode": "HK",
    "mcc": "5812"
  }
}
```

### `CARD_TRANSACTION_SETTLEMENT`

| Field                                 | Type   | Description                                                                                                                                                                                                           |
| :------------------------------------ | :----- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `transactionId`                       | String | Transaction ID, identical to the authorization event's and the statement line's                                                                                                                                       |
| `cardId`                              | String | Card ID                                                                                                                                                                                                               |
| `panLast4`                            | String | Last four digits of the card number                                                                                                                                                                                   |
| `direction`                           | String | `DEBIT` / `CREDIT`. Memo transactions do not push this event                                                                                                                                                          |
| `originalAmount` / `originalCurrency` | String | The original transaction amount and currency                                                                                                                                                                          |
| `postAmount` / `postCurrency`         | String | The settled amount and currency                                                                                                                                                                                       |
| `transactionTime`                     | String | Transaction time in the acquirer's local time                                                                                                                                                                         |
| `transactionCategory`                 | String | `RETAIL` / `CASH` / `RETAIL_FEES` / `CASH_FEES` / `PAYMENT` / `DISPUTE_REGISTER` / `DISPUTE_RELEASE` / `RETAIL_INTEREST` / `CASH_ADVANCE_INTEREST` / `CARD_ANNUAL_FEE` / `CARD_PHYSICAL_FEE` / `CARD_REPLACEMENT_FEE` |
| `merchantName`                        | String | Merchant name                                                                                                                                                                                                         |
| `merchantCountryCode`                 | String | Merchant country, alpha-2                                                                                                                                                                                             |
| `mcc`                                 | String | Merchant category code                                                                                                                                                                                                |

<Warning>
  `transactionCategory` has **three different value domains**: six values on the authorization event (`SALES`, `CASH_ADVANCE`, …), twelve on the settlement event (`RETAIL`, `CASH`, …) and seven on statement lines (`PURCHASE`, `REFUND`, …). Parse them per event type; do not share one enum.
</Warning>

### `CARD_TRANSACTION_DEBT`

Pushed when the card enters a debt state.

| Field                         | Type   | Description                                       |
| :---------------------------- | :----- | :------------------------------------------------ |
| `transactionId`               | String | Transaction ID                                    |
| `cardId`                      | String | The card the debt belongs to                      |
| `debtAmount` / `debtCurrency` | String | The debt amount and currency (transaction basis)  |
| `postAmount` / `postCurrency` | String | The posted amount and currency (settlement basis) |
| `transactionTime`             | String | Transaction time in the acquirer's local time     |
| `merchantName`                | String | Merchant name                                     |

This payload holds two amounts with different meanings (the debt and the posting), so each carries a qualifier instead of a bare `amount`.

***

## Fund events

### `LOW_BALANCE`

Raised when the funding pool falls below the threshold. Threshold setup is covered in [Company maintenance](../company-maintenance).

| Field             | Type   | Description                                       |
| :---------------- | :----- | :------------------------------------------------ |
| `organizationId`  | String | The organization that triggered the alert         |
| `currency`        | String | Funding pool currency: `USD` / `HKD`              |
| `availableAmount` | String | Current available balance, paired with `currency` |
| `thresholdAmount` | String | The threshold that triggered the alert            |

```json theme={null}
{
  "webhookId": "7800000000000001001",
  "webhookType": "LOW_BALANCE",
  "businessId": "e7c9a1f0-3b52-4d8e-9a67-1f2b3c4d5e6f",
  "notificationTime": "2026-08-25T02:30:00Z",
  "data": {
    "organizationId": "e7c9a1f0-3b52-4d8e-9a67-1f2b3c4d5e6f",
    "currency": "USD",
    "availableAmount": "850.25",
    "thresholdAmount": "1000"
  }
}
```

### `BANK_TRANSFER_INFO`

A VA deposit landed. Both success and failure are pushed, distinguished by `status`.

| Field                        | Type   | Description                                                          |
| :--------------------------- | :----- | :------------------------------------------------------------------- |
| `depositId`                  | String | The platform's deposit record ID, matching the deposit-records query |
| `organizationId`             | String | The organization that owns the receiving pool                        |
| `amount` / `currency`        | String | Deposit amount and currency: `USD` / `HKD`                           |
| `payeeAccountNumber`         | String | The receiving virtual account number                                 |
| `referenceCode`              | String | The bank's reference number                                          |
| `payerName`                  | String | Payer name                                                           |
| `payerAccountNumber`         | String | Payer account number                                                 |
| `payerBankCode`              | String | Payer bank code                                                      |
| `status`                     | String | Posting status: `SUCCESS` / `FAILED`                                 |
| `errorCode` / `errorMessage` | String | Returned only on `FAILED`, always as a pair                          |

### `BALANCE_CHANGE`

<Warning>
  **This event's contract is not finalised and its fields may change at any time.** The current implementation forwards the ledger change log as-is, including internal fields that do not follow the conventions in this documentation. Confirm with the platform before integrating; this page does not define its `data` structure.
</Warning>

Three things remain open: which balance dimensions it covers (organization pool balance / free balance / a dedicated-balance card's own balance), how the subject and balance type are identified, and whether every ledger movement is pushed.

## Next steps

* Registering the callback URL, verification and the retry contract: [Webhook configuration](./configuration)
* Receiving and replying to 3DS challenges: [3DS challenges](../3ds-challenges)
* Reconciling through query endpoints: [Statements and transactions](../statements-and-transactions)
