Try it out

Webhooks

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:

All JSON keys use camelCase. The event name itself stays dot-delimited (e.g. virtualcard.transaction.debit) for routing. Amounts are integers in micro-units (1 USD = 1,000,000). Transaction events include displayAmount as the same value expressed as a major-unit float.

Webhook Payload Envelope

Verifying Events

Verifying that these events come from Bitnob is necessary to avoid acting on a forged event.

To verify events, validate the x-bitnob-signature header sent with the event. The HMAC SHA512 signature is the raw event body signed with your secret key.

Notification Retries

When delivering notifications we expect a 200 response. If the response is not 200, we retry up to 3 times with exponential backoff. Use the top-level eventId to deduplicate retries.

Don't rely on webhooks entirely

Note

We recommend that you also poll the card transactions endpoint as a safety net in case webhook delivery fails.

Testing Webhooks

Webhook endpoints must be publicly accessible. While developing locally, use a tunnelling tool like ngrok or localtunnel and point your test webhook URL at the tunnel.

Only use tunnels in test environments to avoid leaking data publicly.

Verify Bitnob Webhook (Node.js - Express)

Wire Conventions for Transaction Events

Transaction events carry three fields that you should branch on:

transactionType — Bitnob's stable internal classification. Values: authorization, settlement, pre_auth_approved, authorization_declined, refund_authorization, refund_settled, 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. declined for declines. terminated for card-terminated declines. failed for failed top-ups / withdrawals / authorizations.

reference — local Bitnob reference (CARD_*-prefixed for backend-generated ones). Use it as the join key against the matching ledger entry.

Some event strings (virtualcard.transaction.contactless, virtualcard.transaction.crossborder, virtualcard.transaction.refund, virtualcard.transaction.reversed) fire with multiple transactionType values — always branch on event first, then transactionType.


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 at the provider.

1
eventIdUUID

Unique identifier for this delivery. Stable across retries — use it as the idempotency key.

2
companyIdUUID

Top-level company identifier. Also repeated inside `data` for convenience.

3
eventString

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

4
idUUID

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

5
statusString

Current status of the card. Typically 'active' immediately after a successful creation.

6
referenceString

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

7
createdStatusString

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

8
reissueBoolean

Whether this card was created as a reissue of an earlier card. `false` for net-new cards.

virtualcard.created.completed

Virtual Card Creation Failed

Fired when the provider rejects a card creation attempt.

1
eventString

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

2
idUUID

Unique identifier for the failed virtual card creation attempt.

3
reasonString

Machine-readable reason code describing why creation failed (e.g. 'card_operation_failed'). Use for debugging.

4
amountNumber

Intended initial funding amount in micro-units (1 USD = 1,000,000).

5
referenceString

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

6
createdStatusString

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

7
customerEmailString

Email of the customer the card was being created for.

virtualcard.created.failed

Virtual Card Terminated Refund

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

1
cardIdUUID

Identifier of the terminated card.

2
idString

Unique transaction identifier for the termination refund.

3
amountNumber

Refund amount in micro-units.

4
referenceString

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

5
narrativeString

Description of the refund (typically 'card_termination_refund').

6
timestampISO 8601 String

When the refund was processed.

7
statusString

Final status. Value: 'completed'.

8
reasonString

Why the card was terminated (e.g. 'customer_requested_termination').

virtualcard.terminated.refund

Virtual Card Regularized

Fired when a card moves out of a degraded or indeterminate state into a stable status (typically active).

1
cardIdUUID

Identifier of the card that was regularized.

2
statusString

New status after regularization (e.g. 'active').

3
referenceString

Reference for the regularization event.

virtualcard.regularized

Virtual Card Expiration

Fired when a card reaches its expiry date and can no longer be used.

1
cardIdUUID

Identifier of the expired card.

2
expiredAtISO 8601 String

Expiration timestamp.

virtualcard.expiration

Virtual Cards Top-Up

Virtual Card Top-up Completed

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

1
cardIdUUID

Identifier of the card that was funded.

2
idString

Unique transaction identifier for the top-up.

3
amountNumber

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

4
referenceString

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

5
narrativeString

Short description of the top-up.

6
statusString

Final status. Value: 'completed'.

7
initiatedFromString

Where the top-up originated (e.g. 'wallet').

8
isTerminatedBoolean

Whether the card was terminated after this top-up. Usually `false`.

virtualcard.topup.completed

Virtual Card Top-up Failed

Fired when a virtual card top-up fails.

Note

The payload shape matches Virtual Card Top-up Completed with status set to "failed". Refer to that section for field explanations.

virtualcard.topup.failed

Virtual Cards Withdrawal

Virtual Card Withdrawal Completed

Fired when a withdrawal on a virtual card succeeds.

1
cardIdUUID

Identifier of the card the funds were withdrawn from.

2
idString

Unique transaction identifier for the withdrawal.

3
amountNumber

Withdrawal amount in micro-units.

4
referenceString

Unique reference for the withdrawal.

5
narrativeString

Short description of the withdrawal.

6
statusString

Final status. Value: 'completed'.

7
initiatedFromString

Where the withdrawal originated (e.g. 'card').

8
isTerminatedBoolean

Whether the card was terminated as a result of this withdrawal.

virtualcard.withdrawal.completed

Virtual Card Withdrawal Failed

Fired when a withdrawal on a virtual card fails.

Note

The payload shape matches Virtual Card Withdrawal Completed with status set to "failed".

virtualcard.withdrawal.failed

Virtual Cards Transaction

Virtual Card Transaction - Debit

Generic settled debit shape. Branch on transactionType 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
cardIdUUID

Identifier of the card that was charged.

2
idString

Unique identifier for this transaction.

3
amountNumber

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

4
displayAmountNumber

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

5
currencyString

ISO 4217 currency code, lowercase (e.g. 'usd').

6
transactionTypeString

Bitnob's stable internal classification (e.g. 'settlement', 'authorization', 'pre_auth_approved', 'contactless_settled', 'contactless_pending').

7
referenceString

Local Bitnob reference. Joins to the matching ledger entry.

8
narrativeString

Human-readable transaction description.

9
timestampISO 8601 String

When the transaction occurred at the network.

10
statusString

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

11
reasonString or null

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

12
merchantNameString

Merchant or acceptor name (e.g. 'netflix_com').

13
merchantMccString

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

14
merchantCountryString

ISO 3166-1 alpha-2 country code of the merchant, lowercase (e.g. 'us').

virtualcard.transaction.debit

Virtual Card Transaction - Authorization

Fired when an authorization hold is approved by the provider; settlement is pending.

Note

Same payload shape as Virtual Card Transaction - Debit with transactionType: "authorization".

virtualcard.transaction.authorization

Virtual Card Transaction - Settlement

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

Note

Same payload shape as Virtual Card Transaction - Debit with transactionType: "settlement". Match against the earlier authorization using reference.

virtualcard.transaction.settlement

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 Virtual Card Transaction - Debit with transactionType: "pre_auth_approved".

virtualcard.transaction.pre-auth.approved

Virtual Card Transaction - Contactless

Contactless / tap-to-pay payment. The same event string fires for both the pending authorization (transactionType: contactless_pending) and the final settled state (transactionType: contactless_settled). Distinguish via transactionType.

Note

Same payload shape as Virtual Card Transaction - Debit.

virtualcard.transaction.contactless (settled)

Virtual Card Transaction - Credit

Generic credit posting on the card (e.g. refund credit, adjustment).

Note

Same payload shape as Virtual Card Transaction - Debit. transactionType will reflect the kind of credit (e.g. refund_settled).

virtualcard.transaction.credit

Virtual Card Transaction - Refund

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

Note

Same payload shape as Virtual Card Transaction - Debit. Branch on transactionType.

virtualcard.transaction.refund

Virtual Card Transaction - Reversed

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

1
cardIdUUID

Identifier of the card whose transaction was reversed.

2
idString

Unique identifier for the reversal transaction.

3
amountNumber

Amount returned to the card in micro-units.

4
displayAmountNumber

Same value as a major-unit float.

5
currencyString

Lowercase ISO 4217 currency code.

6
transactionTypeString

'reversal_settled' for auth reversals; 'cross_border_reversal' for cross-border reversals.

7
referenceString

Reference for the reversal; correlates to the original authorization.

8
narrativeString

Human-readable description, often referencing the original merchant.

9
timestampISO 8601 String

When the reversal occurred at the network.

10
statusString

Final status. Value: 'completed'.

11
reasonString or null

Why the reversal occurred (e.g. 'authorization_expired').

12
merchantNameString

Merchant from the original transaction.

virtualcard.transaction.reversed

Virtual Card Transaction - Verification

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

1
cardIdUUID

Identifier of the card being verified.

2
idString

Unique identifier for the verification transaction.

3
amountNumber

Always 0 for verification.

4
displayAmountNumber

Always 0.0 for verification.

5
currencyString

Lowercase ISO 4217 currency code.

6
transactionTypeString

Value: 'verification'.

7
referenceString

Reference for the verification authorization.

8
narrativeString

Short description of the verification (e.g. 'card_verification_at_uber').

9
timestampISO 8601 String

When the verification occurred.

10
statusString

Value: 'completed'.

11
merchantNameString

Merchant initiating the verification.

12
merchantMccString

ISO 18245 MCC.

13
merchantCountryString

Lowercase ISO 3166-1 alpha-2 country code.

virtualcard.transaction.verification

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 transactionType values:

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

Correlate via reference (CARD_CROSSBORD_*).

1
cardIdUUID

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

2
amountNumber

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

3
chargedAmountNumber

Same as `amount` — the fee amount debited to the wallet.

4
displayAmountNumber

Fee amount as a major-unit float.

5
currencyString

Lowercase ISO 4217 currency code.

6
transactionTypeString

Value: 'cross_border_pending'.

7
settlementCountryString

Country where the original transaction is being settled (ISO 3166-1 alpha-2, lowercase).

8
referenceString

Local reference, prefixed `CARD_CROSSBORD_*`. Use it to correlate with the matching `cross_border_settled` event.

9
narrativeString

Description referencing the underlying authorization id.

10
timestampISO 8601 String

When the fee was debited.

11
statusString

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

12
merchantNameString

Merchant for the underlying purchase.

13
merchantMccString

ISO 18245 MCC.

14
merchantCountryString

Lowercase ISO 3166-1 alpha-2 country code.

virtualcard.transaction.crossborder (pending)

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.

Verification-API-down variant

If Miden's verification API was unreachable when the settlement webhook arrived, the row is recorded at pending and the wire status is "pending" instead of "completed". The transactionType stays cross_border_settled (it is the settlement event, just unverified). Recovery is via the awaiting-verification reconciler.

virtualcard.transaction.crossborder (settled)

Virtual Card Transaction - Terminated Refund

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

1
cardIdUUID

Identifier of the terminated card.

2
idString

Unique identifier of the refund transaction.

3
amountNumber

Refunded amount in micro-units.

4
referenceString

Reference for the refund.

5
narrativeString

Description of the refund.

6
timestampISO 8601 String

When the refund was processed.

7
statusString

Value: 'completed'.

8
reasonString

Why the refund was issued (e.g. 'card_terminated_by_user').

virtualcard.transaction.terminated.refund

Virtual Card Declines

Virtual Card Transaction - Declined

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

1
cardIdUUID

Identifier of the card whose transaction was declined.

2
idString

Unique identifier of the decline event.

3
amountNumber

Attempted amount in micro-units.

4
displayAmountNumber

Attempted amount as a major-unit float.

5
currencyString

Lowercase ISO 4217 currency code.

6
transactionTypeString

Indicates the underlying flow that was declined (e.g. 'debit').

7
referenceString

Reference for the declined attempt.

8
narrativeString

Human-readable description of the attempt.

9
timestampISO 8601 String

When the decline occurred.

10
statusString

Value: 'declined'.

11
reasonString

Machine-readable reason code (e.g. 'insufficient_funds').

12
merchantNameString

Merchant attempting the charge.

13
merchantMccString

ISO 18245 MCC.

14
merchantCountryString

Lowercase ISO 3166-1 alpha-2 country code.

virtualcard.transaction.declined

Virtual Card Transaction - Declined Charge

Fired when a decline-rule violation fee is charged to your USD wallet after repeated declines, or when a card is terminated due to reaching the violation threshold. See the Decline Rule Policy for full details.

1
cardIdUUID

Identifier of the card that triggered the violation.

2
idString

Unique identifier of the fee transaction.

3
feeAmountNumber

Fee amount charged in micro-units.

4
violationCountNumber

Total number of decline-rule violations recorded on this card.

5
referenceString

Reference for the fee transaction.

6
statusString

Value: 'completed'.

virtualcard.transaction.declined.charge

Virtual Card Transaction Declined - Frozen

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

1
cardIdUUID

Identifier of the frozen card.

2
reasonString

Why the card was frozen / declined (e.g. 'card_is_frozen').

3
referenceString

Reference for the declined attempt.

virtualcard.transaction.declined.frozen

Virtual Card Transaction Declined - Terminated

Fired when an authorization is attempted on a terminated card.

1
cardIdUUID

Identifier of the terminated card.

2
idString

Unique identifier of the decline event.

3
amountNumber

Attempted amount in micro-units.

4
displayAmountNumber

Attempted amount as a major-unit float.

5
transactionTypeString

Underlying flow (e.g. 'debit').

6
referenceString

Reference for the declined attempt.

7
statusString

Value: 'declined'.

8
transactionReferenceString

Reference of the original authorization that was attempted.

9
balanceBeforeTerminationNumber

Balance on the card immediately before termination (in micro-units).

10
merchantNameString

Merchant attempting the charge.

11
merchantMccString

ISO 18245 MCC.

12
merchantCountryString

Lowercase ISO 3166-1 alpha-2 country code.

virtualcard.transaction.declined.terminated

Virtual Card Transaction Authorization Failed

Fired when an authorization is rejected (e.g. bad PAN/CVV, AVS failure).

1
cardIdUUID

Identifier of the card used in the failed authorization.

2
idString

Unique identifier of the failed authorization attempt.

3
amountNumber

Attempted amount in micro-units.

4
displayAmountNumber

Attempted amount as a major-unit float.

5
currencyString

Lowercase ISO 4217 currency code.

6
transactionTypeString

Value: 'authorization'.

7
referenceString

Reference for the failed attempt.

8
statusString

Value: 'failed'.

9
reasonString

Why the authorization failed (e.g. 'avs_mismatch').

10
merchantNameString

Merchant attempting the charge.

11
merchantMccString

ISO 18245 MCC.

12
merchantCountryString

Lowercase ISO 3166-1 alpha-2 country code.

virtualcard.transaction.authorization.failed

Virtual Card Transaction Issuer Expiration

Fired when an authorization is declined because the card has expired at the issuer.

1
cardIdUUID

Identifier of the expired card.

2
idString

Unique identifier of the decline event.

3
amountNumber

Attempted amount in micro-units.

4
displayAmountNumber

Attempted amount as a major-unit float.

5
currencyString

Lowercase ISO 4217 currency code.

6
transactionTypeString

Value: 'authorization'.

7
referenceString

Reference for the declined attempt.

8
statusString

Value: 'declined'.

9
reasonString

Value: 'card_expired'.

virtualcard.transaction.issuerexpiration

Activation & KYC

Virtual Card Contactless Activation

Fired when a customer's KYC and card activation for contactless / NFC support completes.

1
cardIdUUID

Identifier of the card that was activated for contactless.

2
customerIdString

Identifier of the customer that owns the card.

3
statusString

Value: 'activated'.

4
activatedAtISO 8601 String

Activation timestamp.

virtualcard.contactless.activation
Did you find this page useful?

Join our Discord