#Webhook events
The complete set of events cryptoinfra sends — 9 of them, generated from docs/openapi.yaml v1.0.0. Nothing outside this list is ever emitted. See Webhooks for signature verification, retries and the polling rules.
| Event | Fires when |
|---|---|
deposit.credited | A deposit cleared confirmations and was credited |
withdrawal.confirmed | A payout confirmed on-chain |
withdrawal.failed | A payout failed and the hold was refunded |
card.kyc | A card holder's KYC status changed |
card.status | A card's status changed (also emitted when an issue order settles) |
card.topup | A card top-up order reached a terminal state (confirmed or failed) |
card.withdraw | A card→wallet withdrawal order reached a terminal state (confirmed or failed) |
card.transaction | A card transaction was created |
card.wallet_otp | A live 3DS wallet OTP is available (notification only — the code is NOT in the payload) |
#deposit.credited
A deposit cleared confirmations and was credited
Delivered as POST to each of your enabled endpoints with x-cryptoinfra-event: deposit.credited and x-cryptoinfra-signature: <hex hmac-sha256 of the raw body>.
Sent to your registered endpoint. Headers: x-cryptoinfra-event: deposit.credited and x-cryptoinfra-signature: <hex hmac-sha256 of the raw body>. Verify the HMAC over the raw bytes before parsing.
#Payload
Schema WebhookDepositCredited.
| Field | Type | Description |
|---|---|---|
depositId | integer | — |
userExternalId | string | — |
assetId | string | — |
chain | string | The chain an address or asset is on. TRON is the only network the platform serves: btc, eth, bsc and sol were retired — historical addresses on them are never handed out again and nothing scans them. Read the value as an open string rather than a closed list, so a chain added later does not break your client. (one of tron) |
amount | string | A monetary value as a decimal string. Always paired with a *_base field that carries the exact integer base-unit value; compute with the _base string. |
fee | string | A monetary value as a decimal string. Always paired with a *_base field that carries the exact integer base-unit value; compute with the _base string. |
amountCredited | string | A monetary value as a decimal string. Always paired with a *_base field that carries the exact integer base-unit value; compute with the _base string. |
txid | string | — |
address | string | — |
fromAddress | string | Who paid it. For a token (USDT, USDC), the from of the Transfer event that credited the user's address — not the transaction's signer, which for an exchange's batch or proxy payout is an operator address. For TRX, the transaction's sender. null only for a deposit recorded before 19 September 2026 whose sender could not be read. |
{
"depositId": 1,
"userExternalId": "string",
"assetId": "string",
"chain": "tron",
"amount": "1.5",
"fee": "1.5",
"amountCredited": "1.5",
"txid": "string",
"address": "string",
"fromAddress": "string"
}#withdrawal.confirmed
A payout confirmed on-chain
Delivered as POST to each of your enabled endpoints with x-cryptoinfra-event: withdrawal.confirmed and x-cryptoinfra-signature: <hex hmac-sha256 of the raw body>.
#Payload
Schema WebhookWithdrawalConfirmed.
| Field | Type | Description |
|---|---|---|
withdrawalId | integer | — |
userExternalId | string | — |
assetId | string | — |
chain | string | The chain an address or asset is on. TRON is the only network the platform serves: btc, eth, bsc and sol were retired — historical addresses on them are never handed out again and nothing scans them. Read the value as an open string rather than a closed list, so a chain added later does not break your client. (one of tron) |
amount | string | A monetary value as a decimal string. Always paired with a *_base field that carries the exact integer base-unit value; compute with the _base string. |
fee | string | A monetary value as a decimal string. Always paired with a *_base field that carries the exact integer base-unit value; compute with the _base string. |
feeAssetId | string | The asset fee is charged in — always equals assetId (the option that could make it differ was retired on 2026-09-05; the field stays so an older consumer keeps working). |
platformFee | string | A monetary value as a decimal string. Always paired with a *_base field that carries the exact integer base-unit value; compute with the _base string. |
toAddress | string | — |
txid | string | null | — |
{
"withdrawalId": 1,
"userExternalId": "string",
"assetId": "string",
"chain": "tron",
"amount": "1.5",
"fee": "1.5",
"feeAssetId": "string",
"platformFee": "1.5",
"toAddress": "string",
"txid": "string"
}#withdrawal.failed
A payout failed and the hold was refunded
Delivered as POST to each of your enabled endpoints with x-cryptoinfra-event: withdrawal.failed and x-cryptoinfra-signature: <hex hmac-sha256 of the raw body>.
#Payload
Schema WebhookWithdrawalFailed.
| Field | Type | Description |
|---|---|---|
withdrawalId | integer | — |
userExternalId | string | — |
assetId | string | — |
chain | string | The chain an address or asset is on. TRON is the only network the platform serves: btc, eth, bsc and sol were retired — historical addresses on them are never handed out again and nothing scans them. Read the value as an open string rather than a closed list, so a chain added later does not break your client. (one of tron) |
amount | string | A monetary value as a decimal string. Always paired with a *_base field that carries the exact integer base-unit value; compute with the _base string. |
fee | string | A monetary value as a decimal string. Always paired with a *_base field that carries the exact integer base-unit value; compute with the _base string. |
feeAssetId | string | The asset fee is charged in — always equals assetId (see withdrawal.confirmed). |
platformFee | string | A monetary value as a decimal string. Always paired with a *_base field that carries the exact integer base-unit value; compute with the _base string. |
toAddress | string | — |
refunded | boolean | Always true — every hold, fee included, was refunded in full. |
error | string | Why. rejected from the dashboard when an owner or finance member refused an awaiting_approval send. |
{
"withdrawalId": 1,
"userExternalId": "string",
"assetId": "string",
"chain": "tron",
"amount": "1.5",
"fee": "1.5",
"feeAssetId": "string",
"platformFee": "1.5",
"toAddress": "string",
"refunded": true,
"error": "string"
}#card.kyc
A card holder's KYC status changed
Delivered as POST to each of your enabled endpoints with x-cryptoinfra-event: card.kyc and x-cryptoinfra-signature: <hex hmac-sha256 of the raw body>.
Signed like every tenant webhook: x-cryptoinfra-event: card.kyc and x-cryptoinfra-signature: <hex hmac-sha256 of the raw body>.
#Payload
Schema WebhookCardKyc.
| Field | Type | Description |
|---|---|---|
userExternalId | string | — |
provider | string | — |
kycStatus | string | Legacy issuer-KYC lifecycle on a card holder. Distinct from KycStatus, the cryptoinfra-owned verdict at /v1/kyc/*. (one of none, pending, approved, rejected) |
remark | string | null | — |
{
"userExternalId": "string",
"provider": "string",
"kycStatus": "none",
"remark": "string"
}#card.status
A card's status changed (also emitted when an issue order settles)
Delivered as POST to each of your enabled endpoints with x-cryptoinfra-event: card.status and x-cryptoinfra-signature: <hex hmac-sha256 of the raw body>.
#Payload
Schema WebhookCardStatus.
| Field | Type | Description |
|---|---|---|
cardId | integer | — |
userExternalId | string | — |
provider | string | Present on the generic path only. |
status | integer | string | Provider RAW status code (integer) on a card-lifecycle change; a CardOrderStatus string on the issue-order variant. |
providerStatus | string | — |
reason | string | Optional; present on card status-change events. |
refundAmount | string | Informational remaining-balance figure on card close; no ledger entry is posted for it in v1. |
orderId | integer | Present only on the issue-order settlement variant. |
kind | string | close_fee is the close fee charged to the cardholder on DELETE /v1/cards/{id}: amount is 0, fee is the charge. (one of topup, withdraw, issue_fee, close_fee) |
assetId | string | Present on the issue-order settlement variant. |
amount | string | Present on the issue-order settlement variant. |
fee | string | Present on the issue-order settlement variant. |
gas | string | Issue-order variant — gas for the on-chain send. |
merchantFee | string | Issue-order variant — your own fee. |
total | string | Issue-order variant — everything the cardholder was debited; mirror this |
error | string | Present on the issue-order settlement variant when it failed. |
{
"cardId": 1,
"userExternalId": "string",
"provider": "string",
"status": 1,
"providerStatus": "string",
"reason": "string",
"refundAmount": "string",
"orderId": 1,
"kind": "topup",
"assetId": "string",
"amount": "1.5",
"fee": "1.5",
"gas": "1.5",
"merchantFee": "1.5",
"total": "1.5",
"error": "string"
}#card.topup
A card top-up order reached a terminal state (confirmed or failed)
Delivered as POST to each of your enabled endpoints with x-cryptoinfra-event: card.topup and x-cryptoinfra-signature: <hex hmac-sha256 of the raw body>.
#Payload
Schema WebhookCardOrder.
| Field | Type | Description |
|---|---|---|
orderId | integer | — |
kind | string | close_fee is the close fee charged to the cardholder on DELETE /v1/cards/{id}: amount is 0, fee is the charge. (one of topup, withdraw, issue_fee, close_fee) |
txid | string | null | The hash of the on-chain send behind the order, as on GET /v1/cards/orders/{id}; null when there was none. |
userExternalId | string | — |
cardId | integer | null | — |
assetId | string | — |
amount | string | A monetary value as a decimal string. Always paired with a *_base field that carries the exact integer base-unit value; compute with the _base string. |
fee | string | A monetary value as a decimal string. Always paired with a *_base field that carries the exact integer base-unit value; compute with the _base string. |
gas | string | The network fee actually CHARGED for the on-chain send behind the order — what the cardholder was debited, not the quoted figure. |
merchantFee | string | Your own fee on this order, credited to your balance. |
total | string | Everything the cardholder was debited — amount + fee + gas + merchantFee. |
amountCredited | string | A monetary value as a decimal string. Always paired with a *_base field that carries the exact integer base-unit value; compute with the _base string. |
amountCreditedBase | string | Exact integer amount in the asset's smallest unit. (pattern ^[0-9]+$) |
status | string | funding = the cardholder has been charged and the money is on its way to the card rail on-chain. requested = confirmed on-chain, waiting for the rail to show the credit. sent = the rail has been asked, outcome still pending. confirmed = settled. failed = definitively failed; the WHOLE charge — amount, fee, gas and merchant_fee, i.e. total — is refunded to the balance it was taken from, the cardholder's, in the same moment (top-up / issue only — a withdraw holds nothing). Nothing goes anywhere else: not to the card, not to your treasury. When the on-chain send behind a load reverted (provider_status: send_failed), no coins left the cardholder's address either, so on-chain and ledger agree. reversed is reserved for manual operator reversals of a confirmed order. (one of funding, requested, sent, confirmed, failed, reversed) |
error | string | — |
providerFee | string | — |
{
"orderId": 1,
"kind": "topup",
"txid": "string",
"userExternalId": "string",
"cardId": 1,
"assetId": "string",
"amount": "1.5",
"fee": "1.5",
"gas": "1.5",
"merchantFee": "1.5",
"total": "1.5",
"amountCredited": "1.5",
"amountCreditedBase": "1500000000",
"status": "funding",
"error": "string",
"providerFee": "string"
}#card.withdraw
A card→wallet withdrawal order reached a terminal state (confirmed or failed)
Delivered as POST to each of your enabled endpoints with x-cryptoinfra-event: card.withdraw and x-cryptoinfra-signature: <hex hmac-sha256 of the raw body>.
#Payload
Schema WebhookCardOrder.
| Field | Type | Description |
|---|---|---|
orderId | integer | — |
kind | string | close_fee is the close fee charged to the cardholder on DELETE /v1/cards/{id}: amount is 0, fee is the charge. (one of topup, withdraw, issue_fee, close_fee) |
txid | string | null | The hash of the on-chain send behind the order, as on GET /v1/cards/orders/{id}; null when there was none. |
userExternalId | string | — |
cardId | integer | null | — |
assetId | string | — |
amount | string | A monetary value as a decimal string. Always paired with a *_base field that carries the exact integer base-unit value; compute with the _base string. |
fee | string | A monetary value as a decimal string. Always paired with a *_base field that carries the exact integer base-unit value; compute with the _base string. |
gas | string | The network fee actually CHARGED for the on-chain send behind the order — what the cardholder was debited, not the quoted figure. |
merchantFee | string | Your own fee on this order, credited to your balance. |
total | string | Everything the cardholder was debited — amount + fee + gas + merchantFee. |
amountCredited | string | A monetary value as a decimal string. Always paired with a *_base field that carries the exact integer base-unit value; compute with the _base string. |
amountCreditedBase | string | Exact integer amount in the asset's smallest unit. (pattern ^[0-9]+$) |
status | string | funding = the cardholder has been charged and the money is on its way to the card rail on-chain. requested = confirmed on-chain, waiting for the rail to show the credit. sent = the rail has been asked, outcome still pending. confirmed = settled. failed = definitively failed; the WHOLE charge — amount, fee, gas and merchant_fee, i.e. total — is refunded to the balance it was taken from, the cardholder's, in the same moment (top-up / issue only — a withdraw holds nothing). Nothing goes anywhere else: not to the card, not to your treasury. When the on-chain send behind a load reverted (provider_status: send_failed), no coins left the cardholder's address either, so on-chain and ledger agree. reversed is reserved for manual operator reversals of a confirmed order. (one of funding, requested, sent, confirmed, failed, reversed) |
error | string | — |
providerFee | string | — |
{
"orderId": 1,
"kind": "topup",
"txid": "string",
"userExternalId": "string",
"cardId": 1,
"assetId": "string",
"amount": "1.5",
"fee": "1.5",
"gas": "1.5",
"merchantFee": "1.5",
"total": "1.5",
"amountCredited": "1.5",
"amountCreditedBase": "1500000000",
"status": "funding",
"error": "string",
"providerFee": "string"
}#card.transaction
A card transaction was created
Delivered as POST to each of your enabled endpoints with x-cryptoinfra-event: card.transaction and x-cryptoinfra-signature: <hex hmac-sha256 of the raw body>.
#Payload
Schema WebhookCardTransaction.
| Field | Type | Description |
|---|---|---|
cardId | integer | — |
userExternalId | string | — |
currency | string | null | — |
createAt | integer | null | Provider epoch timestamp. |
transaction | object | — |
transaction.recordNo | string | null | — |
transaction.transactionId | string | null | — |
transaction.originalTransactionId | string | null | On a refund (171) |
transaction.transType | integer | null | 170 payment, 171 refund, 172 card fee, 173 ATM withdrawal, 174 pre-authorization. |
transaction.transStatus | integer | null | 1 processing, 2 success, 3 failed. |
transaction.merchantName | string | null | — |
transaction.merchantCategoryCode | string | null | — |
transaction.localCurrency | string | null | — |
transaction.localCurrencyAmt | string | null | — |
transaction.transCurrency | string | null | — |
transaction.transCurrencyAmt | string | null | — |
transaction.feeAmount | string | null | — |
transaction.feeCurrency | string | null | — |
transaction.balance | string | null | — |
transaction.respCodeDesc | string | null | — |
{
"cardId": 1,
"userExternalId": "string",
"currency": "string",
"createAt": 1,
"transaction": {
"recordNo": "string",
"transactionId": "string",
"originalTransactionId": "string",
"transType": 1,
"transStatus": 1,
"merchantName": "string",
"merchantCategoryCode": "string",
"localCurrency": "string",
"localCurrencyAmt": "string",
"transCurrency": "string",
"transCurrencyAmt": "string",
"feeAmount": "string",
"feeCurrency": "string",
"balance": "string",
"respCodeDesc": "string"
}
}#card.wallet_otp
A live 3DS wallet OTP is available (notification only — the code is NOT in the payload)
Delivered as POST to each of your enabled endpoints with x-cryptoinfra-event: card.wallet_otp and x-cryptoinfra-signature: <hex hmac-sha256 of the raw body>.
#Payload
Schema WebhookCardWalletOtp.
| Field | Type | Description |
|---|---|---|
cardId | integer | — |
userExternalId | string | — |
provider | string | — |
providerCardId | string | — |
event | string | (one of wallet_otp_available) |
name | string | Provider event name. |
otpKind | string | Which code it is — branch on this, not on name. (one of payment_3ds, wallet_provisioning) |
retrievable | boolean | True while the code can be fetched. |
retrievalPath | string | — |
expiresInSeconds | integer | — |
{
"cardId": 1,
"userExternalId": "string",
"provider": "string",
"providerCardId": "string",
"event": "wallet_otp_available",
"name": "string",
"otpKind": "payment_3ds",
"retrievable": true,
"retrievalPath": "/v1/cards/102/otp",
"expiresInSeconds": 600
}