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

> ## Agent Instructions
> Bitnob API requests authenticate with an `X-Auth-Client` API key header against https://api.bitnob.com.
> Always use the sandbox environment for testing. Never put live API keys in sample code.
> Money amounts are integers in the smallest unit of the currency (satoshis for BTC, cents for fiat). Never use floats.
> Every state-changing request takes a client-supplied `reference` and must be treated as idempotent on retry.
> Quotes expire. Re-quote rather than reusing a stale quote id.

# Virtual card webhook events

> Virtual card event types and the payload fields each one carries, from creation to transactions and KYC.

Learn how to listen for events that happen on your virtual cards.

A webhook is a URL on your server where we send payloads for card-related events. For example, when a customer's card is successfully topped up, we immediately deliver a `virtualcard.topup.completed` event to your endpoint. Whenever you receive a webhook from us, return a `200 OK` to avoid retries.

All payloads share the same envelope:

The envelope has `event_id`, `event`, and `data`, and the company ID is inside `data`. Keys inside `data` are mostly camelCase, but transaction events use snake\_case for `display_amount`, `transaction_type`, `merchant_name`, `merchant_mcc`, and `merchant_country`. The `event` name stays dot-delimited (e.g. `virtualcard.transaction.settlement`) for routing. Amounts are integers in **micro-units** (1 USD = 1,000,000). Transaction events include `display_amount` as the same value expressed as a major-unit float.

```json theme={null}
{
  "event_id": "<uuid, unique per event, safe to use as idempotency key>",
  "event": "<event name>",
  "data": { ... }
}
```

> [!NOTE]
>
> Signature verification, retries, source IPs, and local testing work the same way for every Bitnob Business API webhook. They're covered once, on [Webhooks](https://www.bitnob.dev/api-reference/webhooks.md).

## Wire conventions for transaction events

Transaction events carry three fields that you should branch on:

**`transaction_type`**: Bitnob's stable internal classification. Values: `authorization`, `settlement`, `pre_auth_approved`, `authorization_declined`, `refund_authorization`, `refund_settled`, `reversal`, `reversal_settled`, `cross_border_pending`, `cross_border_settled`, `cross_border_reversal`, `contactless_pending`, `contactless_settled`, `verification`.

**`status`**: wire-level outcome. `completed` for settled debits / credits / reversals / verifications. `pending` for cross-border auth still awaiting settlement. `decline` for declines. `terminated` for card-terminated declines. `failed` for failed top-ups and withdrawals. Termination lifecycle events can also send `complete`.

**`reference`**: local Bitnob reference (`CARD_*`-prefixed for backend-generated ones). Use it as the join key against the matching ledger entry. Each event gets its own reference, so don't use it to match one event to another.

Some event strings (`virtualcard.transaction.contactless`, `virtualcard.transaction.crossborder`, `virtualcard.transaction.refund`, `virtualcard.transaction.reversed`) fire with multiple `transaction_type` values. Always branch on `event` first, then `transaction_type`.

## Virtual cards webhooks

Virtual card webhooks are fired when a customer performs any virtual card action.

### Virtual card created successfully

Fired when a virtual card is successfully created.

1. `event_id` (uuid)

   Unique identifier for this event. Stable across retries: use it as the idempotency key.

2. `companyId` (uuid)

   Company identifier, inside `data`.

3. `event` (string)

   Webhook event name. Value: 'virtualcard.created.completed'.

4. `id` (uuid)

   Unique identifier of the newly created virtual card. Use this to retrieve card details or reference the card in support cases.

5. `status` (string)

   Current status of the card, usually 'active'.

6. `reference` (string)

   Unique reference string for the card creation request. Use for deduplication, auditing, and reconciliation.

7. `createdStatus` (string)

   Outcome of the creation request. Value 'completed' confirms the card was issued without errors.

8. Optional fields

   `name`, `balanceAmount`, `balanceCurrency`, `cardBrand`, and `lastFour` are included when known.

```json theme={null}
{
  "event_id": "7f3a9c2b-4d1e-4f5a-8b9c-1d2e3f4a5b6c",
  "event": "virtualcard.created.completed",
  "data": {
    "id": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "status": "active",
    "reference": "card_ref_7f1a2b3c4d5e",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "createdStatus": "completed"
  }
}
```

### Virtual card creation failed

Fired when a card creation attempt is rejected.

1. `event` (string)

   Webhook event name. Value: 'virtualcard.created.failed'.

2. `id` (uuid)

   Unique identifier for the failed virtual card creation attempt.

3. `failureReason` (string)

   Why creation failed. This is free text, so don't branch on the wording.

4. `reference` (string)

   Unique reference string used to identify the creation request across systems.

5. `status` (string)

   Status of the card.

6. `createdStatus` (string)

   Final outcome of the creation request. Value: 'failed'.

7. Optional fields

   `name` and `balanceCurrency` are included when known.

```json theme={null}
{
  "event_id": "8e4b0d3c-5e2f-4a6b-9c0d-2e3f4a5b6c7d",
  "event": "virtualcard.created.failed",
  "data": {
    "id": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "reference": "card_ref_7f1a2b3c4d5e",
    "status": "failed",
    "createdStatus": "failed",
    "balanceCurrency": "USD",
    "failureReason": "The card could not be created."
  }
}
```

### Virtual card terminated refund

Fired when a card is terminated and any remaining balance is refunded.

1. `cardId` (uuid)

   Identifier of the terminated card.

2. `id` (string)

   Unique transaction identifier for the termination refund. Can be empty if no refund was recorded.

3. `amount` (number)

   Refund amount in micro-units. Can be 0.

4. `reference` (string)

   Reference for the termination refund. Joins to the matching ledger entry.

5. `status` (string)

   Final status, usually 'completed'. It can also be 'complete' when no refund was recorded.

6. `reason` (string)

Why the card was terminated. Free text, and left out when there's no reason.

```json theme={null}
{
  "event_id": "9f5c1e4d-6f3a-4b7c-a0d1-3f4a5b6c7d8e",
  "event": "virtualcard.terminated.refund",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "tx_term_9a8b7c6d5e4f",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 5250000,
    "reference": "termination_ref_2a3b4c5d",
    "status": "completed",
    "reason": "Card terminated at the customer's request"
  }
}
```

### Virtual card regularized

Fired when a terminated card's balance is corrected. It uses the same fields as a top-up, with `isTerminated: true`.

```json theme={null}
{
  "event_id": "a06d2f5e-7a4b-4c8d-b1e2-4a5b6c7d8e9f",
  "event": "virtualcard.regularized",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "tx_regularize_a1b2c3d4e5f6",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 1000000,
    "reference": "regularize_ref_3b4c5d6e",
    "narrative": "Terminated card regularized, balance set to 1000000",
    "status": "completed",
    "isTerminated": true
  }
}
```

### Virtual card expiration

Sent as an advance notice that a card is approaching its expiry date.

1. `cardId` (uuid)

   Identifier of the card.

2. `reason` (string)

   The notice text, such as 'Card expires in 30' or 'Card expires on 2026-06-30'.

3. `reference` (string)

   The card's reference.

```json theme={null}
{
  "event_id": "b17e3a6f-8b5c-4d9e-a2f3-5b6c7d8e9f0a",
  "event": "virtualcard.expiration",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "reason": "Card expires in 30",
    "reference": "card_ref_7a8b9c0d"
  }
}
```

### Virtual cards top-Up

### Virtual card top-up completed

Fired when a top-up to a virtual card succeeds.

1. `cardId` (uuid)

   Identifier of the card that was funded.

2. `id` (string)

   Unique transaction identifier for the top-up.

3. `amount` (number)

   Top-up amount in micro-units (1 USD = 1,000,000).

4. `reference` (string)

   Unique reference for the top-up. Joins to your wallet ledger entry.

5. `status` (string)

   Final status. Value: 'completed'.

6. `narrative` (string, optional)

   Short description of the top-up.

7. `initiatedFrom` (string, optional)

   Where the top-up started: 'api', 'dashboard', 'webhook', or 'admin'.

8. `isTerminated` (boolean, optional)

   Whether the card is terminated. Usually \`false\`.

9. `deposit_reference`, `deposit_transaction_id`, `deposit_tx_hash` (string, optional)

Sent only when a crypto deposit funded the card automatically.

```json theme={null}
{
  "event_id": "c28f4b7a-9c6d-4e0f-a3a4-6c7d8e9f0a1b",
  "event": "virtualcard.topup.completed",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "fund_tx_1a2b3c4d5e6f",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 25000000,
    "reference": "topup_ref_9f8e7d6c",
    "narrative": "Card topup",
    "status": "completed",
    "initiatedFrom": "api",
    "isTerminated": false
  }
}
```

### Virtual card top-up failed

Fired when a virtual card top-up fails.

> [!NOTE]
>
> This carries `cardId`, `id`, `companyId`, `amount`, `reference`, `status`, and `reason`. `status` is `"failed"`, and `reason` says why. The deposit fields can also appear.

```json theme={null}
{
  "event_id": "d39a5c8b-ad7e-4f1a-b4b5-7d8e9f0a1b2c",
  "event": "virtualcard.topup.failed",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "fund_tx_1a2b3c4d5e6f",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 25000000,
    "reference": "topup_ref_9f8e7d6c",
    "status": "failed",
    "reason": "The top-up could not be completed."
  }
}
```

### Virtual cards withdrawal

### Virtual card withdrawal completed

Fired when a withdrawal on a virtual card succeeds.

1. `cardId` (uuid)

   Identifier of the card the funds were withdrawn from.

2. `id` (string)

   Unique transaction identifier for the withdrawal.

3. `amount` (number)

   Withdrawal amount in micro-units.

4. `reference` (string)

   Unique reference for the withdrawal.

5. `status` (string)

   Final status. Value: 'completed'.

6. `narrative` (string, optional)

   Short description of the withdrawal.

7. `initiatedFrom` (string, optional)

   Where the withdrawal started: 'api', 'dashboard', 'webhook', or 'admin'.

8. `isTerminated` (boolean, optional)

Whether the card is terminated.

```json theme={null}
{
  "event_id": "e4ab6d9c-be8f-4a2b-95c6-8e9f0a1b2c3d",
  "event": "virtualcard.withdrawal.completed",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "wd_tx_5e4d3c2b1a09",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 10000000,
    "reference": "withdraw_ref_6a5b4c3d",
    "narrative": "Card withdrawal",
    "status": "completed",
    "initiatedFrom": "api",
    "isTerminated": false
  }
}
```

### Virtual card withdrawal failed

Fired when a withdrawal on a virtual card fails.

> [!NOTE]
>
> This carries `cardId`, `id`, `companyId`, `amount`, `reference`, `status`, and `reason`. `status` is `"failed"`, and `reason` says why.

```json theme={null}
{
  "event_id": "f5bc7eaa-cf9a-4b3c-86d7-9f0a1b2c3d4e",
  "event": "virtualcard.withdrawal.failed",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "wd_tx_5e4d3c2b1a09",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 10000000,
    "reference": "withdraw_ref_6a5b4c3d",
    "status": "failed",
    "reason": "The withdrawal could not be completed."
  }
}
```

### Virtual cards transaction

### Transaction payload shape

The shared shape for transaction events. Branch on `transaction_type` to distinguish `settlement`, `authorization`, `pre_auth_approved`, `contactless_settled`, and `contactless_pending`. The dedicated event names below are what is actually emitted on the wire: branch on `event` first.

1. `cardId` (uuid)

   Identifier of the card that was charged.

2. `id` (string)

   Unique identifier for this transaction.

3. `amount` (number)

   Debit amount in micro-units (1 USD = 1,000,000).

4. `display_amount` (number)

   Same value expressed as a major-unit float (e.g. 1.599 for \$1.599).

5. `currency` (string)

   Currency code as the card network sends it, such as `USD`. Compare it case-insensitively.

6. `transaction_type` (string)

   Bitnob's stable internal classification (e.g. 'settlement', 'authorization', 'pre\_auth\_approved', 'contactless\_settled', 'contactless\_pending').

7. `reference` (string)

   Local Bitnob reference. Joins to the matching ledger entry.

8. `narrative` (string)

   Human-readable transaction description.

9. `timestamp` (ISO 8601 string)

   When the transaction occurred at the network.

10. `status` (string)

Wire-level outcome (e.g. 'completed').

11. `reason` (string or null)

Additional context if the network supplied it. Often null for successful debits.

12. `merchant_name` (string)

Merchant or acceptor name (e.g. 'netflix\_com').

13. `merchant_mcc` (string)

ISO 18245 Merchant Category Code (e.g. '4899').

14. `merchant_country` (string)

Merchant country as the card network sends it, such as `USA`. It can be two or three letters, so compare it case-insensitively.

```json theme={null}
{
  "event_id": "06cd8fab-da0b-4c4d-87e8-0a1b2c3d4e5f",
  "event": "virtualcard.transaction.settlement",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "tx_debit_a1b2c3d4e5f6",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 1599000,
    "display_amount": 1.599,
    "currency": "USD",
    "transaction_type": "settlement",
    "reference": "purchase_ref_1234abcd",
    "narrative": "Settlement at netflix_com",
    "timestamp": "2026-05-16T14:22:10Z",
    "status": "completed",
    "reason": null,
    "merchant_name": "netflix_com",
    "merchant_mcc": "4899",
    "merchant_country": "USA"
  }
}
```

### Virtual card transaction - authorization

Fired when an authorization hold is approved; settlement is pending.

> [!NOTE]
>
> Same payload shape as **Transaction payload shape** with `transaction_type: "authorization"`.

```json theme={null}
{
  "event_id": "17de90bc-eb1c-4d5e-8f9a-1b2c3d4e5f60",
  "event": "virtualcard.transaction.authorization",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "tx_auth_b2c3d4e5f6a1",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 4500000,
    "display_amount": 4.5,
    "currency": "USD",
    "transaction_type": "authorization",
    "reference": "auth_ref_2345bcde",
    "narrative": "Authorization at amazon_com",
    "timestamp": "2026-05-16T14:22:10Z",
    "status": "completed",
    "reason": null,
    "merchant_name": "amazon_com",
    "merchant_mcc": "5942",
    "merchant_country": "USA"
  }
}
```

### Virtual card transaction - settlement

Fired when a previously-authorized transaction is settled by the merchant.

> [!NOTE]
>
> Same payload shape as **Transaction payload shape** with `transaction_type: "settlement"`. The settlement has its own `reference`, so it won't match the authorization's.

```json theme={null}
{
  "event_id": "28ef01cd-fc2d-4e6f-9a0b-2c3d4e5f6071",
  "event": "virtualcard.transaction.settlement",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "tx_settle_c3d4e5f6a1b2",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 4500000,
    "display_amount": 4.5,
    "currency": "USD",
    "transaction_type": "settlement",
    "reference": "settle_ref_3456cdef",
    "narrative": "Settlement at amazon_com",
    "timestamp": "2026-05-16T15:01:33Z",
    "status": "completed",
    "reason": null,
    "merchant_name": "amazon_com",
    "merchant_mcc": "5942",
    "merchant_country": "USA"
  }
}
```

### Virtual card transaction - pre-Auth approved

Fired when a pre-authorization hold is placed (e.g. hotel deposit, gas-pump pre-auth).

> [!NOTE]
>
> Same payload shape as **Transaction payload shape** with `transaction_type: "pre_auth_approved"`.

```json theme={null}
{
  "event_id": "39f012de-0d3e-4f70-ab1c-3d4e5f607182",
  "event": "virtualcard.transaction.pre-auth.approved",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "tx_preauth_d4e5f6a1b2c3",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 10000000,
    "display_amount": 10.0,
    "currency": "USD",
    "transaction_type": "pre_auth_approved",
    "reference": "preauth_ref_4567def0",
    "narrative": "Pre-authorization at shell_oil_12345",
    "timestamp": "2026-05-16T16:10:20Z",
    "status": "completed",
    "reason": null,
    "merchant_name": "shell_oil_12345",
    "merchant_mcc": "5541",
    "merchant_country": "USA"
  }
}
```

### Virtual card transaction - contactless

Contactless / tap-to-pay payment. The same `event` string fires for both the pending authorization (`transaction_type: contactless_pending`) and the final settled state (`transaction_type: contactless_settled`). `status` is `completed` on both, so distinguish via `transaction_type`.

> [!NOTE]
>
> Same payload shape as **Transaction payload shape**.

```json theme={null}
{
  "event_id": "4a012e3f-1e4f-4081-bc2d-4e5f60718293",
  "event": "virtualcard.transaction.contactless",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "tx_tap_e5f6a1b2c3d4",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 750000,
    "display_amount": 0.75,
    "currency": "USD",
    "transaction_type": "contactless_settled",
    "reference": "CARD_CONTLESS_5678ef01",
    "narrative": "Contactless transaction at Starbucks #1234",
    "timestamp": "2026-05-16T08:15:02Z",
    "status": "completed",
    "reason": null,
    "merchant_name": "Starbucks #1234",
    "merchant_mcc": "5814",
    "merchant_country": "USA"
  }
}
```

### Virtual card transaction - refund

Merchant-initiated refund. The same `event` string fires for both the refund authorization (`transaction_type: refund_authorization`) and the refund settlement (`transaction_type: refund_settled`).

> [!NOTE]
>
> Same payload shape as **Transaction payload shape**. Branch on `transaction_type`.

```json theme={null}
{
  "event_id": "7d345162-4172-43b4-8f50-718293a45b6c",
  "event": "virtualcard.transaction.refund",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "tx_refund_a1b2c3d4e5f7",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 4500000,
    "display_amount": 4.5,
    "currency": "USD",
    "transaction_type": "refund_settled",
    "reference": "refund_ref_7890a123",
    "narrative": "Refund settled at amazon_com",
    "timestamp": "2026-05-16T18:22:50Z",
    "status": "completed",
    "merchant_name": "amazon_com",
    "merchant_mcc": "5942",
    "merchant_country": "USA"
  }
}
```

### Virtual card transaction - reversed

Fired when an authorization is reversed before settlement. Same `event` string is also emitted for cross-border reversals, `transaction_type` is `reversal_settled` for auth reversals and `cross_border_reversal` for cross-border.

1. `cardId` (uuid)

   Identifier of the card whose transaction was reversed.

2. `id` (string)

   Unique identifier for the reversal transaction.

3. `amount` (number)

   Amount returned to the card in micro-units.

4. `display_amount` (number)

   Same value as a major-unit float.

5. `currency` (string)

   Currency code as the card network sends it, such as `USD`. Compare it case-insensitively.

6. `transaction_type` (string)

   'reversal\_settled' for auth reversals; 'cross\_border\_reversal' for cross-border reversals.

7. `reference` (string)

   Reference for the reversal. It doesn't match the original authorization's reference.

8. `narrative` (string)

   Human-readable description, such as 'Reversal at amazon\_com'.

9. `timestamp` (ISO 8601 string)

   When the reversal occurred at the network.

10. `status` (string)

Final status. Value: 'completed'.

11. `reason` (string or null)

Extra context if the network supplied it. Often null.

12. `merchant_name` (string)

Merchant from the original transaction.

```json theme={null}
{
  "event_id": "8e456273-5283-44c5-a061-8293a45b6c7d",
  "event": "virtualcard.transaction.reversed",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "tx_rev_b2c3d4e5f7a1",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 4500000,
    "display_amount": 4.5,
    "currency": "USD",
    "transaction_type": "reversal_settled",
    "reference": "reversal_ref_8901b234",
    "narrative": "Reversal at amazon_com",
    "timestamp": "2026-05-16T15:30:00Z",
    "status": "completed",
    "reason": null,
    "merchant_name": "amazon_com",
    "merchant_mcc": "5942",
    "merchant_country": "USA"
  }
}
```

### Virtual card transaction - verification

Zero-dollar verification authorization (e.g. Uber, Netflix card-on-file checks).

1. `cardId` (uuid)

   Identifier of the card being verified.

2. `id` (string)

   Unique identifier for the verification transaction.

3. `amount` (number)

   Always 0 for verification.

4. `display_amount` (number)

   Always 0.0 for verification.

5. `currency` (string)

   Currency code as the card network sends it, such as `USD`. Compare it case-insensitively.

6. `transaction_type` (string)

   Value: 'verification'.

7. `reference` (string)

   Reference for the verification authorization.

8. `narrative` (string)

   Short description of the verification (e.g. 'Card verification at uber\_trip').

9. `timestamp` (ISO 8601 string)

   When the verification occurred.

10. `status` (string)

Value: 'completed'.

11. `merchant_name` (string)

Merchant initiating the verification.

12. `merchant_mcc` (string)

ISO 18245 MCC.

13. `merchant_country` (string)

Merchant country as the card network sends it, such as `USA`. It can be two or three letters, so compare it case-insensitively.

```json theme={null}
{
  "event_id": "9f567384-6394-45d6-8172-93a45b6c7d8e",
  "event": "virtualcard.transaction.verification",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "tx_verify_c3d4e5f7a1b2",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 0,
    "display_amount": 0.0,
    "currency": "USD",
    "transaction_type": "verification",
    "reference": "verify_ref_9012c345",
    "narrative": "Card verification at uber_trip",
    "timestamp": "2026-05-16T11:00:00Z",
    "status": "completed",
    "reason": null,
    "merchant_name": "uber_trip",
    "merchant_mcc": "4121",
    "merchant_country": "NLD"
  }
}
```

### Virtual card transaction - cross-border (pending)

Fired when a cross-border fee is debited from the **company wallet** during the auth phase of an international transaction. The amount on this payload is the FEE, not the purchase principal, the purchase principal lands later as `cross_border_settled` against the card balance.

**Two money paths:** Cross-border transactions emit this `event` string twice with different `transaction_type` values:

* `cross_border_pending` → company wallet fee debit (this event)
* `cross_border_settled` → card balance purchase principal debit

Each event has its own `reference`, so don't use it to match them.

1. `cardId` (uuid)

   Identifier of the card used for the cross-border transaction.

2. `amount` (number)

   Cross-border FEE charged to the company wallet, in micro-units. NOT the purchase principal.

3. `chargedAmount` (number)

   Same as \`amount\`, the fee amount debited to the wallet.

4. `display_amount` (number)

   Fee amount as a major-unit float.

5. `currency` (string)

   Currency code as the card network sends it, such as `USD`. Compare it case-insensitively.

6. `transaction_type` (string)

   Value: 'cross\_border\_pending'.

7. `settlementCountry` (string)

   Country where the original transaction is being settled, as the card network sends it. It can be two or three letters, so compare it case-insensitively.

8. `reference` (string)

   Local reference for this event.

9. `narrative` (string)

   Description referencing the underlying authorization id.

10. `timestamp` (ISO 8601 string)

When the fee was debited.

11. `status` (string)

Value: 'pending', reflects the purchase lifecycle, not the fee (the fee debit itself is final).

12. `merchant_name` (string)

Merchant for the underlying purchase.

13. `merchant_mcc` (string)

ISO 18245 MCC.

14. `merchant_country` (string)

Merchant country as the card network sends it, such as `USA`. It can be two or three letters, so compare it case-insensitively.

```json theme={null}
{
  "event_id": "5b123f40-2f50-4192-9d3e-5f607182934a",
  "event": "virtualcard.transaction.crossborder",
  "data": {
    "chargedAmount": 250000,
    "narrative": "Cross-border fee for authorization 64972501 on card c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "settlementCountry": "fr",
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 250000,
    "display_amount": 0.25,
    "currency": "USD",
    "transaction_type": "cross_border_pending",
    "timestamp": "2026-05-16T09:45:11Z",
    "status": "pending",
    "reference": "CARD_CROSSBORD_054A205F6BF5",
    "merchant_name": "Hotel Paris",
    "merchant_mcc": "7011",
    "merchant_country": "FRA"
  }
}
```

### Virtual card transaction - cross-border (settled)

Fired when the purchase principal of a cross-border transaction is deducted from the **card balance**. No company-wallet movement on this event, the company-wallet fee debit (if any) was emitted separately as `cross_border_pending`.

**Unverified variant:** If the settlement couldn't be verified with the card issuer when it arrived, it's recorded as pending and the wire `status` is `"pending"` instead of `"completed"`. The `transaction_type` stays `cross_border_settled`, and verification is retried automatically.

```json theme={null}
{
  "event_id": "5c234051-3061-42a3-ae4f-60718293a45b",
  "event": "virtualcard.transaction.crossborder",
  "data": {
    "chargedAmount": 12500000,
    "narrative": "Cross-border transaction at Hotel Paris",
    "settlementCountry": "fr",
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 12500000,
    "display_amount": 12.5,
    "currency": "USD",
    "transaction_type": "cross_border_settled",
    "timestamp": "2026-05-16T14:13:44Z",
    "status": "completed",
    "reference": "CARD_CROSSBORD_92812767",
    "merchant_name": "Hotel Paris",
    "merchant_mcc": "7011",
    "merchant_country": "FRA"
  }
}
```

### Virtual card transaction - terminated refund

Per-transaction refund posted as part of card termination cleanup. Funds returned to the float or master account.

1. `cardId` (uuid)

   Identifier of the terminated card.

2. `id` (string)

   Unique identifier of the refund transaction.

3. `amount` (number)

   Refunded amount in micro-units.

4. `reference` (string)

   Reference for the refund.

5. `narrative` (string)

   Description of the refund.

6. `timestamp` (ISO 8601 string)

   When the refund was processed.

7. `status` (string)

   Value: 'completed'.

8. `reason` (null)

Always null on this event.

```json theme={null}
{
  "event_id": "06cdeafb-da0b-4c4d-b8e9-0a1b2c3d4e5f",
  "event": "virtualcard.transaction.terminated.refund",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "tx_term_refund_c3d4e5f8a1b2",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 5250000,
    "reference": "term_refund_ref_6789d012",
    "narrative": "refund_of_remaining_balance_on_terminated_card",
    "timestamp": "2026-05-16T10:31:15Z",
    "status": "completed",
    "reason": null
  }
}
```

### Virtual card declines

### Virtual card transaction - declined

Fired when a purchase attempt is declined (e.g. insufficient funds, CVV mismatch).

1. `cardId` (uuid)

   Identifier of the card whose transaction was declined.

2. `id` (string)

   Unique identifier of the decline event.

3. `amount` (number)

   Attempted amount in micro-units.

4. `display_amount` (number)

   Attempted amount as a major-unit float.

5. `currency` (string)

   Currency code as the card network sends it, such as `USD`. Compare it case-insensitively.

6. `transaction_type` (string)

   Indicates the flow that was declined (e.g. 'authorization\_declined').

7. `reference` (string)

   Reference for the declined attempt.

8. `narrative` (string)

   Human-readable description of the attempt.

9. `timestamp` (ISO 8601 string)

   When the decline occurred.

10. `status` (string)

Value: 'decline'.

11. `reason` (string)

Why the transaction was declined. This is free text, not a fixed code.

12. `merchant_name` (string)

Merchant attempting the charge.

13. `merchant_mcc` (string)

ISO 18245 MCC.

14. `merchant_country` (string)

Merchant country as the card network sends it, such as `USA`. It can be two or three letters, so compare it case-insensitively.

```json theme={null}
{
  "event_id": "a0678495-74a5-46e7-9283-a45b6c7d8e9f",
  "event": "virtualcard.transaction.declined",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "tx_decline_d4e5f7a1b2c3",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 9999000,
    "display_amount": 9.999,
    "currency": "USD",
    "transaction_type": "authorization_declined",
    "reference": "decline_ref_0123d456",
    "narrative": "Authorization declined at walmart_com",
    "timestamp": "2026-05-16T12:30:45Z",
    "status": "decline",
    "reason": "Insufficient funds",
    "merchant_name": "walmart_com",
    "merchant_mcc": "5411",
    "merchant_country": "USA"
  }
}
```

### Virtual card transaction - declined charge

Fired after a decline-rule violation fee is debited from your USD wallet. See the [Decline Rule Policy](https://www.bitnob.dev/docs/card-issuing/card-fees.md) for full details.

1. `cardId` (uuid)

   Identifier of the card that triggered the violation.

2. `id` (string)

   Identifier of the declined transaction that triggered the fee.

3. `feeAmount` (number)

   Fee amount charged in micro-units.

4. `violationCount` (number)

   Decline-rule violations on this card since its last approved authorization.

5. `reference` (string)

   Reference of the declined transaction that triggered the fee.

6. `status` (string)

Value: 'completed'.

```json theme={null}
{
  "event_id": "b17895a6-85b6-47f8-a394-b56c7d8e9f0a",
  "event": "virtualcard.transaction.declined.charge",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "tx_decline_fee_e5f7a1b2c3d4",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "feeAmount": 750000,
    "violationCount": 2,
    "reference": "decline_fee_ref_1234e567",
    "status": "completed"
  }
}
```

### Virtual card transaction declined - frozen

Fired when an authorization is declined because the card is frozen (usually triggered by a fraud rule).

1. `cardId` (uuid)

   Identifier of the frozen card.

2. `reason` (string)

   Why the card was frozen. Free text, for example 'Card frozen due to declined authorization'.

3. `reference` (string)

The card's reference.

```json theme={null}
{
  "event_id": "c289a6b7-96c7-4809-b4a5-c6d7e8f90a1b",
  "event": "virtualcard.transaction.declined.frozen",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "reason": "Card frozen due to declined authorization",
    "reference": "card_ref_7a8b9c0d"
  }
}
```

### Virtual card transaction declined - terminated

Fired when the card issuer terminates a card after a declined authorization. The card's remaining balance is returned. Terminations from decline rules don't use this event: they arrive as `virtualcard.terminated.refund`.

1. `cardId` (uuid)

   Identifier of the terminated card.

2. `id` (string)

   Unique identifier of the transaction.

3. `amount` (number)

   The card's balance before termination, in micro-units.

4. `display_amount` (number)

   The same amount as a major-unit float.

5. `currency` (string)

   Currency code as the card network sends it, such as `USD`. Compare it case-insensitively.

6. `transaction_type` (string)

   Value: 'authorization'.

7. `reference` (string)

   Reference for the transaction.

8. `status` (string)

   Value: 'terminated'.

9. `transactionReference` (string)

   Same value as `reference`.

10. `balanceBeforeTermination` (number)

Same value as `amount`.

11. `merchant_name` (string)

Merchant attempting the charge.

12. `merchant_mcc` (string)

ISO 18245 MCC.

13. `merchant_country` (string)

Merchant country as the card network sends it, such as `USA`. It can be two or three letters, so compare it case-insensitively.

```json theme={null}
{
  "event_id": "d39ab7c8-a7d8-491a-85b6-d7e8f90a1b2c",
  "event": "virtualcard.transaction.declined.terminated",
  "data": {
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "tx_term_decline_f7a1b2c3d4e5",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 2500000,
    "display_amount": 2.5,
    "currency": "USD",
    "transaction_type": "authorization",
    "reference": "term_decline_ref_3456a789",
    "status": "terminated",
    "transactionReference": "term_decline_ref_3456a789",
    "balanceBeforeTermination": 2500000,
    "merchant_name": "spotify",
    "merchant_mcc": "5815",
    "merchant_country": "SWE"
  }
}
```

### Virtual card transaction issuer expiration

Fired when the card issuer reverses an authorization because it expired. The amount returns to the card.

1. `cardId` (uuid)

   Identifier of the card.

2. `id` (string)

   Unique identifier of the reversal.

3. `amount` (number)

   Reversed amount in micro-units.

4. `display_amount` (number)

   Reversed amount as a major-unit float.

5. `currency` (string)

   Currency code as the card network sends it, such as `USD`. Compare it case-insensitively.

6. `transaction_type` (string)

   Value: 'reversal'.

7. `reference` (string)

   Reference for the reversal.

8. `status` (string)

   Value: 'completed'.

9. `reason` (string)

Description of the reversal, such as 'Issuer expiration reversal at amazon\_com'.

```json theme={null}
{
  "event_id": "f5bcd9ea-c9fa-4b3c-a7d8-f90a1b2c3d4e",
  "event": "virtualcard.transaction.issuerexpiration",
  "data": {
    "reason": "Issuer expiration reversal at amazon_com",
    "cardId": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "id": "tx_expired_b2c3d4e5f8a1",
    "companyId": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "amount": 1999000,
    "display_amount": 1.999,
    "currency": "USD",
    "transaction_type": "reversal",
    "reference": "expired_ref_5678c901",
    "status": "completed"
  }
}
```

### Activation & KYC

KYC event payloads are on [Card KYC](https://www.bitnob.dev/docs/card-issuing/card-kyc.md#webhooks).

### Virtual card contactless activation

Sent to the card's `webhook_url` when a contactless activation code is issued. Keys in this payload are snake\_case.

1. `company_id` (uuid)

   Identifier of the company.

2. `customer_id` (uuid)

   Identifier of the customer that owns the card.

3. `card_id` (uuid)

   Identifier of the card.

4. `activation_code` (string)

   The activation code. Treat it as a secret and don't log it.

5. `email` (string)

   The customer's email address.

```json theme={null}
{
  "event_id": "17defb0c-eb1c-4d5e-89fa-1b2c3d4e5f60",
  "event": "virtualcard.contactless.activation",
  "data": {
    "company_id": "5f4e3d2c_1b0a_4d8c_9e7f_6a5b4c3d2e1f",
    "customer_id": "customer_1a2b3c4d5e6f",
    "card_id": "c8f1a3b2_9e4d_4f1a_b8c7_2d3e5f6a7b8c",
    "activation_code": "123456",
    "email": "customer@example.com"
  }
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.