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.
Signature verification, retries, source IPs, and local testing work the same way for every Bitnob Business API webhook. They’re covered once, on Webhooks.
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.-
event_id(uuid) Unique identifier for this event. Stable across retries: use it as the idempotency key. -
companyId(uuid) Company identifier, insidedata. -
event(string) Webhook event name. Value: ‘virtualcard.created.completed’. -
id(uuid) Unique identifier of the newly created virtual card. Use this to retrieve card details or reference the card in support cases. -
status(string) Current status of the card, usually ‘active’. -
reference(string) Unique reference string for the card creation request. Use for deduplication, auditing, and reconciliation. -
createdStatus(string) Outcome of the creation request. Value ‘completed’ confirms the card was issued without errors. -
Optional fields
name,balanceAmount,balanceCurrency,cardBrand, andlastFourare included when known.
Virtual card creation failed
Fired when a card creation attempt is rejected.-
event(string) Webhook event name. Value: ‘virtualcard.created.failed’. -
id(uuid) Unique identifier for the failed virtual card creation attempt. -
failureReason(string) Why creation failed. This is free text, so don’t branch on the wording. -
reference(string) Unique reference string used to identify the creation request across systems. -
status(string) Status of the card. -
createdStatus(string) Final outcome of the creation request. Value: ‘failed’. -
Optional fields
nameandbalanceCurrencyare included when known.
Virtual card terminated refund
Fired when a card is terminated and any remaining balance is refunded.-
cardId(uuid) Identifier of the terminated card. -
id(string) Unique transaction identifier for the termination refund. Can be empty if no refund was recorded. -
amount(number) Refund amount in micro-units. Can be 0. -
reference(string) Reference for the termination refund. Joins to the matching ledger entry. -
status(string) Final status, usually ‘completed’. It can also be ‘complete’ when no refund was recorded. -
reason(string)
Virtual card regularized
Fired when a terminated card’s balance is corrected. It uses the same fields as a top-up, withisTerminated: true.
Virtual card expiration
Sent as an advance notice that a card is approaching its expiry date.-
cardId(uuid) Identifier of the card. -
reason(string) The notice text, such as ‘Card expires in 30’ or ‘Card expires on 2026-06-30’. -
reference(string) The card’s reference.
Virtual cards top-Up
Virtual card top-up completed
Fired when a top-up to a virtual card succeeds.-
cardId(uuid) Identifier of the card that was funded. -
id(string) Unique transaction identifier for the top-up. -
amount(number) Top-up amount in micro-units (1 USD = 1,000,000). -
reference(string) Unique reference for the top-up. Joins to your wallet ledger entry. -
status(string) Final status. Value: ‘completed’. -
narrative(string, optional) Short description of the top-up. -
initiatedFrom(string, optional) Where the top-up started: ‘api’, ‘dashboard’, ‘webhook’, or ‘admin’. -
isTerminated(boolean, optional) Whether the card is terminated. Usually `false`. -
deposit_reference,deposit_transaction_id,deposit_tx_hash(string, optional)
Virtual card top-up failed
Fired when a virtual card top-up fails.This carries
cardId, id, companyId, amount, reference, status, and reason. status is "failed", and reason says why. The deposit fields can also appear.Virtual cards withdrawal
Virtual card withdrawal completed
Fired when a withdrawal on a virtual card succeeds.-
cardId(uuid) Identifier of the card the funds were withdrawn from. -
id(string) Unique transaction identifier for the withdrawal. -
amount(number) Withdrawal amount in micro-units. -
reference(string) Unique reference for the withdrawal. -
status(string) Final status. Value: ‘completed’. -
narrative(string, optional) Short description of the withdrawal. -
initiatedFrom(string, optional) Where the withdrawal started: ‘api’, ‘dashboard’, ‘webhook’, or ‘admin’. -
isTerminated(boolean, optional)
Virtual card withdrawal failed
Fired when a withdrawal on a virtual card fails.This carries
cardId, id, companyId, amount, reference, status, and reason. status is "failed", and reason says why.Virtual cards transaction
Transaction payload shape
The shared shape for transaction events. Branch ontransaction_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.
-
cardId(uuid) Identifier of the card that was charged. -
id(string) Unique identifier for this transaction. -
amount(number) Debit amount in micro-units (1 USD = 1,000,000). -
display_amount(number) Same value expressed as a major-unit float (e.g. 1.599 for $1.599). -
currency(string) Currency code as the card network sends it, such asUSD. Compare it case-insensitively. -
transaction_type(string) Bitnob’s stable internal classification (e.g. ‘settlement’, ‘authorization’, ‘pre_auth_approved’, ‘contactless_settled’, ‘contactless_pending’). -
reference(string) Local Bitnob reference. Joins to the matching ledger entry. -
narrative(string) Human-readable transaction description. -
timestamp(ISO 8601 string) When the transaction occurred at the network. -
status(string)
reason(string or null)
merchant_name(string)
merchant_mcc(string)
merchant_country(string)
USA. It can be two or three letters, so compare it case-insensitively.
Virtual card transaction - authorization
Fired when an authorization hold is approved; settlement is pending.Same payload shape as Transaction payload shape with
transaction_type: "authorization".Virtual card transaction - settlement
Fired when a previously-authorized transaction is settled by the merchant.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.Virtual card transaction - pre-Auth approved
Fired when a pre-authorization hold is placed (e.g. hotel deposit, gas-pump pre-auth).Same payload shape as Transaction payload shape with
transaction_type: "pre_auth_approved".Virtual card transaction - contactless
Contactless / tap-to-pay payment. The sameevent 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.
Same payload shape as Transaction payload shape.
Virtual card transaction - refund
Merchant-initiated refund. The sameevent string fires for both the refund authorization (transaction_type: refund_authorization) and the refund settlement (transaction_type: refund_settled).
Same payload shape as Transaction payload shape. Branch on
transaction_type.Virtual card transaction - reversed
Fired when an authorization is reversed before settlement. Sameevent string is also emitted for cross-border reversals, transaction_type is reversal_settled for auth reversals and cross_border_reversal for cross-border.
-
cardId(uuid) Identifier of the card whose transaction was reversed. -
id(string) Unique identifier for the reversal transaction. -
amount(number) Amount returned to the card in micro-units. -
display_amount(number) Same value as a major-unit float. -
currency(string) Currency code as the card network sends it, such asUSD. Compare it case-insensitively. -
transaction_type(string) ‘reversal_settled’ for auth reversals; ‘cross_border_reversal’ for cross-border reversals. -
reference(string) Reference for the reversal. It doesn’t match the original authorization’s reference. -
narrative(string) Human-readable description, such as ‘Reversal at amazon_com’. -
timestamp(ISO 8601 string) When the reversal occurred at the network. -
status(string)
reason(string or null)
merchant_name(string)
Virtual card transaction - verification
Zero-dollar verification authorization (e.g. Uber, Netflix card-on-file checks).-
cardId(uuid) Identifier of the card being verified. -
id(string) Unique identifier for the verification transaction. -
amount(number) Always 0 for verification. -
display_amount(number) Always 0.0 for verification. -
currency(string) Currency code as the card network sends it, such asUSD. Compare it case-insensitively. -
transaction_type(string) Value: ‘verification’. -
reference(string) Reference for the verification authorization. -
narrative(string) Short description of the verification (e.g. ‘Card verification at uber_trip’). -
timestamp(ISO 8601 string) When the verification occurred. -
status(string)
merchant_name(string)
merchant_mcc(string)
merchant_country(string)
USA. It can be two or three letters, so compare it case-insensitively.
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 ascross_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
reference, so don’t use it to match them.
-
cardId(uuid) Identifier of the card used for the cross-border transaction. -
amount(number) Cross-border FEE charged to the company wallet, in micro-units. NOT the purchase principal. -
chargedAmount(number) Same as `amount`, the fee amount debited to the wallet. -
display_amount(number) Fee amount as a major-unit float. -
currency(string) Currency code as the card network sends it, such asUSD. Compare it case-insensitively. -
transaction_type(string) Value: ‘cross_border_pending’. -
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. -
reference(string) Local reference for this event. -
narrative(string) Description referencing the underlying authorization id. -
timestamp(ISO 8601 string)
status(string)
merchant_name(string)
merchant_mcc(string)
merchant_country(string)
USA. It can be two or three letters, so compare it case-insensitively.
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 ascross_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.
Virtual card transaction - terminated refund
Per-transaction refund posted as part of card termination cleanup. Funds returned to the float or master account.-
cardId(uuid) Identifier of the terminated card. -
id(string) Unique identifier of the refund transaction. -
amount(number) Refunded amount in micro-units. -
reference(string) Reference for the refund. -
narrative(string) Description of the refund. -
timestamp(ISO 8601 string) When the refund was processed. -
status(string) Value: ‘completed’. -
reason(null)
Virtual card declines
Virtual card transaction - declined
Fired when a purchase attempt is declined (e.g. insufficient funds, CVV mismatch).-
cardId(uuid) Identifier of the card whose transaction was declined. -
id(string) Unique identifier of the decline event. -
amount(number) Attempted amount in micro-units. -
display_amount(number) Attempted amount as a major-unit float. -
currency(string) Currency code as the card network sends it, such asUSD. Compare it case-insensitively. -
transaction_type(string) Indicates the flow that was declined (e.g. ‘authorization_declined’). -
reference(string) Reference for the declined attempt. -
narrative(string) Human-readable description of the attempt. -
timestamp(ISO 8601 string) When the decline occurred. -
status(string)
reason(string)
merchant_name(string)
merchant_mcc(string)
merchant_country(string)
USA. It can be two or three letters, so compare it case-insensitively.
Virtual card transaction - declined charge
Fired after a decline-rule violation fee is debited from your USD wallet. See the Decline Rule Policy for full details.-
cardId(uuid) Identifier of the card that triggered the violation. -
id(string) Identifier of the declined transaction that triggered the fee. -
feeAmount(number) Fee amount charged in micro-units. -
violationCount(number) Decline-rule violations on this card since its last approved authorization. -
reference(string) Reference of the declined transaction that triggered the fee. -
status(string)
Virtual card transaction declined - frozen
Fired when an authorization is declined because the card is frozen (usually triggered by a fraud rule).-
cardId(uuid) Identifier of the frozen card. -
reason(string) Why the card was frozen. Free text, for example ‘Card frozen due to declined authorization’. -
reference(string)
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 asvirtualcard.terminated.refund.
-
cardId(uuid) Identifier of the terminated card. -
id(string) Unique identifier of the transaction. -
amount(number) The card’s balance before termination, in micro-units. -
display_amount(number) The same amount as a major-unit float. -
currency(string) Currency code as the card network sends it, such asUSD. Compare it case-insensitively. -
transaction_type(string) Value: ‘authorization’. -
reference(string) Reference for the transaction. -
status(string) Value: ‘terminated’. -
transactionReference(string) Same value asreference. -
balanceBeforeTermination(number)
amount.
merchant_name(string)
merchant_mcc(string)
merchant_country(string)
USA. It can be two or three letters, so compare it case-insensitively.
Virtual card transaction issuer expiration
Fired when the card issuer reverses an authorization because it expired. The amount returns to the card.-
cardId(uuid) Identifier of the card. -
id(string) Unique identifier of the reversal. -
amount(number) Reversed amount in micro-units. -
display_amount(number) Reversed amount as a major-unit float. -
currency(string) Currency code as the card network sends it, such asUSD. Compare it case-insensitively. -
transaction_type(string) Value: ‘reversal’. -
reference(string) Reference for the reversal. -
status(string) Value: ‘completed’. -
reason(string)
Activation & KYC
KYC event payloads are on Card KYC.Virtual card contactless activation
Sent to the card’swebhook_url when a contactless activation code is issued. Keys in this payload are snake_case.
-
company_id(uuid) Identifier of the company. -
customer_id(uuid) Identifier of the customer that owns the card. -
card_id(uuid) Identifier of the card. -
activation_code(string) The activation code. Treat it as a secret and don’t log it. -
email(string) The customer’s email address.