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 and identifier values use snake_case. 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 display_amount as the same value expressed as a major-unit float.
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 event_id to deduplicate retries.
Don't rely on webhooks entirely
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.
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_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 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 at the provider.
Unique identifier for this delivery. Stable across retries — use it as the idempotency key.
Top-level company identifier. Also repeated inside `data` for convenience.
Webhook event name. Value: 'virtualcard.created.completed'.
Unique identifier of the newly created virtual card. Use this to retrieve card details or reference the card in support cases.
Current status of the card. Typically 'active' immediately after a successful creation.
Unique reference string for the card creation request. Use for deduplication, auditing, and reconciliation.
Outcome of the creation request. Value 'completed' confirms the card was issued without errors.
Whether this card was created as a reissue of an earlier card. `false` for net-new cards.
Virtual Card Creation Failed
Fired when the provider rejects a card creation attempt.
Webhook event name. Value: 'virtualcard.created.failed'.
Unique identifier for the failed virtual card creation attempt.
Machine-readable reason code describing why creation failed (e.g. 'card_operation_failed'). Use for debugging.
Intended initial funding amount in micro-units (1 USD = 1,000,000).
Unique reference string used to identify the creation request across systems.
Final outcome of the creation request. Value: 'failed'.
Email of the customer the card was being created for.
Virtual Card Terminated Refund
Fired when a card is terminated and any remaining balance is refunded.
Identifier of the terminated card.
Unique transaction identifier for the termination refund.
Refund amount in micro-units.
Reference for the termination refund. Joins to the matching ledger entry.
Description of the refund (typically 'card_termination_refund').
When the refund was processed.
Final status. Value: 'completed'.
Why the card was terminated (e.g. 'customer_requested_termination').
Virtual Card Regularized
Fired when a card moves out of a degraded or indeterminate state into a stable status (typically active).
Identifier of the card that was regularized.
New status after regularization (e.g. 'active').
Reference for the regularization event.
Virtual Card Expiration
Fired when a card reaches its expiry date and can no longer be used.
Identifier of the expired card.
Expiration timestamp.
Virtual Cards Top-Up
Virtual Card Top-up Completed
Fired when a top-up to a virtual card succeeds.
Identifier of the card that was funded.
Unique transaction identifier for the top-up.
Top-up amount in micro-units (1 USD = 1,000,000).
Unique reference for the top-up. Joins to your wallet ledger entry.
Short description of the top-up.
Final status. Value: 'completed'.
Where the top-up originated (e.g. 'wallet').
Whether the card was terminated after this top-up. Usually `false`.
Virtual Card Top-up Failed
Fired when a virtual card top-up fails.
The payload shape matches Virtual Card Top-up Completed with status set to "failed". Refer to that section for field explanations.
Virtual Cards Withdrawal
Virtual Card Withdrawal Completed
Fired when a withdrawal on a virtual card succeeds.
Identifier of the card the funds were withdrawn from.
Unique transaction identifier for the withdrawal.
Withdrawal amount in micro-units.
Unique reference for the withdrawal.
Short description of the withdrawal.
Final status. Value: 'completed'.
Where the withdrawal originated (e.g. 'card').
Whether the card was terminated as a result of this withdrawal.
Virtual Card Withdrawal Failed
Fired when a withdrawal on a virtual card fails.
The payload shape matches Virtual Card Withdrawal Completed with status set to "failed".
Virtual Cards Transaction
Virtual Card Transaction - Debit
Generic settled debit shape. 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.
Identifier of the card that was charged.
Unique identifier for this transaction.
Debit amount in micro-units (1 USD = 1,000,000).
Same value expressed as a major-unit float (e.g. 1.599 for $1.599).
ISO 4217 currency code, lowercase (e.g. 'usd').
Bitnob's stable internal classification (e.g. 'settlement', 'authorization', 'pre_auth_approved', 'contactless_settled', 'contactless_pending').
Local Bitnob reference. Joins to the matching ledger entry.
Human-readable transaction description.
When the transaction occurred at the network.
Wire-level outcome (e.g. 'completed').
Additional context if the network supplied it. Often null for successful debits.
Merchant or acceptor name (e.g. 'netflix_com').
ISO 18245 Merchant Category Code (e.g. '4899').
ISO 3166-1 alpha-2 country code of the merchant, lowercase (e.g. 'us').
Virtual Card Transaction - Authorization
Fired when an authorization hold is approved by the provider; settlement is pending.
Same payload shape as Virtual Card Transaction - Debit with transaction_type: "authorization".
Virtual Card Transaction - Settlement
Fired when a previously-authorized transaction is settled by the merchant.
Same payload shape as Virtual Card Transaction - Debit with transaction_type: "settlement". Match against the earlier authorization using reference.
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 Virtual Card Transaction - Debit with transaction_type: "pre_auth_approved".
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). Distinguish via transaction_type.
Same payload shape as Virtual Card Transaction - Debit.
Virtual Card Transaction - Credit
Generic credit posting on the card (e.g. refund credit, adjustment).
Same payload shape as Virtual Card Transaction - Debit. transaction_type will reflect the kind of credit (e.g. refund_settled).
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).
Same payload shape as Virtual Card Transaction - Debit. Branch on transaction_type.
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.
Identifier of the card whose transaction was reversed.
Unique identifier for the reversal transaction.
Amount returned to the card in micro-units.
Same value as a major-unit float.
Lowercase ISO 4217 currency code.
'reversal_settled' for auth reversals; 'cross_border_reversal' for cross-border reversals.
Reference for the reversal; correlates to the original authorization.
Human-readable description, often referencing the original merchant.
When the reversal occurred at the network.
Final status. Value: 'completed'.
Why the reversal occurred (e.g. 'authorization_expired').
Merchant from the original transaction.
Virtual Card Transaction - Verification
Zero-dollar verification authorization (e.g. Uber, Netflix card-on-file checks).
Identifier of the card being verified.
Unique identifier for the verification transaction.
Always 0 for verification.
Always 0.0 for verification.
Lowercase ISO 4217 currency code.
Value: 'verification'.
Reference for the verification authorization.
Short description of the verification (e.g. 'card_verification_at_uber').
When the verification occurred.
Value: 'completed'.
Merchant initiating the verification.
ISO 18245 MCC.
Lowercase ISO 3166-1 alpha-2 country code.
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.
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
Correlate via reference (CARD_CROSSBORD_*).
Identifier of the card used for the cross-border transaction.
Cross-border FEE charged to the company wallet, in micro-units. NOT the purchase principal.
Same as `amount` — the fee amount debited to the wallet.
Fee amount as a major-unit float.
Lowercase ISO 4217 currency code.
Value: 'cross_border_pending'.
Country where the original transaction is being settled (ISO 3166-1 alpha-2, lowercase).
Local reference, prefixed `CARD_CROSSBORD_*`. Use it to correlate with the matching `cross_border_settled` event.
Description referencing the underlying authorization id.
When the fee was debited.
Value: 'pending' — reflects the purchase lifecycle, not the fee (the fee debit itself is final).
Merchant for the underlying purchase.
ISO 18245 MCC.
Lowercase ISO 3166-1 alpha-2 country code.
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.
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 transaction_type stays cross_border_settled (it is the settlement event, just unverified). Recovery is via the awaiting-verification reconciler.
Virtual Card Transaction - Terminated Refund
Per-transaction refund posted as part of card termination cleanup. Funds returned to the float or master account.
Identifier of the terminated card.
Unique identifier of the refund transaction.
Refunded amount in micro-units.
Reference for the refund.
Description of the refund.
When the refund was processed.
Value: 'completed'.
Why the refund was issued (e.g. 'card_terminated_by_user').
Virtual Card Declines
Virtual Card Transaction - Declined
Fired when a purchase attempt is declined (e.g. insufficient funds, CVV mismatch).
Identifier of the card whose transaction was declined.
Unique identifier of the decline event.
Attempted amount in micro-units.
Attempted amount as a major-unit float.
Lowercase ISO 4217 currency code.
Indicates the underlying flow that was declined (e.g. 'debit').
Reference for the declined attempt.
Human-readable description of the attempt.
When the decline occurred.
Value: 'declined'.
Machine-readable reason code (e.g. 'insufficient_funds').
Merchant attempting the charge.
ISO 18245 MCC.
Lowercase ISO 3166-1 alpha-2 country code.
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.
Identifier of the card that triggered the violation.
Unique identifier of the fee transaction.
Fee amount charged in micro-units.
Total number of decline-rule violations recorded on this card.
Reference for the fee transaction.
Value: 'completed'.
Virtual Card Transaction Declined - Frozen
Fired when an authorization is declined because the card is frozen (usually triggered by a fraud rule).
Identifier of the frozen card.
Why the card was frozen / declined (e.g. 'card_is_frozen').
Reference for the declined attempt.
Virtual Card Transaction Declined - Terminated
Fired when an authorization is attempted on a terminated card.
Identifier of the terminated card.
Unique identifier of the decline event.
Attempted amount in micro-units.
Attempted amount as a major-unit float.
Underlying flow (e.g. 'debit').
Reference for the declined attempt.
Value: 'declined'.
Reference of the original authorization that was attempted.
Balance on the card immediately before termination (in micro-units).
Merchant attempting the charge.
ISO 18245 MCC.
Lowercase ISO 3166-1 alpha-2 country code.
Virtual Card Transaction Authorization Failed
Fired when an authorization is rejected (e.g. bad PAN/CVV, AVS failure).
Identifier of the card used in the failed authorization.
Unique identifier of the failed authorization attempt.
Attempted amount in micro-units.
Attempted amount as a major-unit float.
Lowercase ISO 4217 currency code.
Value: 'authorization'.
Reference for the failed attempt.
Value: 'failed'.
Why the authorization failed (e.g. 'avs_mismatch').
Merchant attempting the charge.
ISO 18245 MCC.
Lowercase ISO 3166-1 alpha-2 country code.
Virtual Card Transaction Issuer Expiration
Fired when an authorization is declined because the card has expired at the issuer.
Identifier of the expired card.
Unique identifier of the decline event.
Attempted amount in micro-units.
Attempted amount as a major-unit float.
Lowercase ISO 4217 currency code.
Value: 'authorization'.
Reference for the declined attempt.
Value: 'declined'.
Value: 'card_expired'.
Activation & KYC
Virtual Card Contactless Activation
Fired when a customer's KYC and card activation for contactless / NFC support completes.
Identifier of the card that was activated for contactless.
Identifier of the customer that owns the card.
Value: 'activated'.
Activation timestamp.
Virtual Card KYC Completed
Fired when KYC verification succeeds for a customer.
Unique identifier of the KYC outcome.
Identifier of the customer that was verified.
Email of the verified customer.
Outcome detail (e.g. 'verification_passed').
Value: true.
Virtual Card KYC Failed
Fired when KYC verification fails for a customer.
The payload shape matches Virtual Card KYC Completed with kyc_passed: false and a reason describing the failure.