Overview
Virtual accounts emit one of two webhook payload types depending on what's changing. Changes to the account resource itself emit x-zh-hook-payload-type header value of virtual_account.status_changed, while activity on deposits landing in that account emit virtual_account_deposit.status_changed. Both events track their respective resource as it moves through its lifecycle - the account through creation, closure, blocking, and suspension; the deposit through receipt, screening, and payouts - but each uses its own set of status values reflecting its distinct pipeline. The table below consolidates the possible statuses across both event types and which type each status applies to.
Virtual Account Events
Subscribe to this event by setting the x-zh-hook-payload-type header value to virtual_account.status_changed.
A virtual_account.status_changed webhook is emitted every time a virtual account transitions between states - for example, when the account is first opened and becomes active, when zerohash blocks it, or when it's closed or suspended.
Virtual Account Statuses
The status field communicates the current state of the virtual account. Values are returned in lowercase.
| Status | Description |
|---|---|
active | Virtual account has been successfully created and can receive deposits. |
closed | Virtual account has been closed. Terminal. |
blocked | zerohash has blocked the account; it can no longer receive deposits. Terminal. |
failed | zerohash could not open or had to reject the account. Terminal. |
Note:REST API responses (e.g.
GET /virtual_accounts/{id}) use uppercase status values (PENDING,ACTIVE,CLOSED,BLOCKED,FAILED). This webhook uses lowercase values for the same states. The two are not interchangeable strings.
Virtual Account Payload Schema
| Field | Type | Description |
|---|---|---|
event_type | string | Always virtual_account.status_changed. |
virtual_account_id | string | Unique identifier for the virtual account. |
participant_code | string | Participant code of the end customer that owns the account. |
platform_code | string | Platform the account belongs to. |
provider_type | string | Banking provider backing the account. Currently only CRB. |
status | string | Current account status. See Virtual Account Statuses for possible values. |
bank_details | object | The account's bank details. Present only when status is active. |
bank_details.account_number | string | The account's account number. |
bank_details.routing_number | string | The account's routing number. |
bank_details.provider_account_id | string | The banking provider's internal identifier for the account. |
status_detail | object | Additional context on why the account entered its current status. Present only for blockedand failed (and suspended which is not yet live, but coming soon). |
status_detail.code | string | Stable, machine-readable reason code. |
status_detail.message | Human-readable description of the code. Do not parse, may be reworded. | |
status_detail.at | string (ISO 8601) | Timestamp the status detail was recorded. |
created_at | timestamp | Timestamp the account was created. |
updated_at | timestamp | Timestamp the account was last updated. |
Note:
event_typeis specific to this webhook. Thevirtual_account_deposit.status_changedwebhook does not include anevent_typefield, itstypefield is a resource type indicator (alwaysDEPOSIT), not an event name.
Recovering from a failed Virtual Account
The status_detail.code on a failed event determines whether the participant can try again:
VA_CREATION_DECLINED: terminal for the participant. Any furtherPOST /virtual_accountsfor that participant, or for a different participant sharing the same tax ID, is rejected with409 CUSTOMER_TERMINAL.VA_CREATION_FAILED: terminal only for that specific account. A newPOST /virtual_accountsfor the same participant, using a newIdempotency-Key, creates a fresh account normally.
virtual_account.status_changed Payload Examples
virtual_account.status_changed Payload ExamplesActive
{
"virtual_account_id": "47d7dfe4-fcae-45fe-9242-7e14795f449c",
"participant_code": "CUST01",
"platform_code": "PLAT01",
"provider_type": "CRB",
"status": "active",
"bank_details": {
"account_number": "1234567890",
"routing_number": "021000021",
"provider_account_id": "crb_ac_9f13e2"
},
"created_at": "2026-08-20T14:32:01Z",
"updated_at": "2026-08-20T14:32:01Z"
}Closed
{
"virtual_account_id": "47d7dfe4-fcae-45fe-9242-7e14795f449c",
"participant_code": "CUST01",
"platform_code": "PLAT01",
"provider_type": "CRB",
"status": "closed",
"created_at": "2026-08-20T14:32:01Z",
"updated_at": "2026-08-25T09:10:44Z"
}Blocked
{
"virtual_account_id": "47d7dfe4-fcae-45fe-9242-7e14795f449c",
"participant_code": "CUST01",
"platform_code": "PLAT01",
"provider_type": "CRB",
"status": "blocked",
"status_detail": {
"code": "VA_BLOCKED",
"message": "zerohash has blocked this virtual account and it can no longer receive deposits. Contact zerohash support for next steps.",
"at": "2026-08-25T09:10:44Z"
},
"created_at": "2026-08-20T14:32:01Z",
"updated_at": "2026-08-25T09:10:44Z"
}Failed - Creation declined
{
"virtual_account_id": "47d7dfe4-fcae-45fe-9242-7e14795f449c",
"participant_code": "CUST01",
"platform_code": "PLAT01",
"provider_type": "CRB",
"status": "failed",
"status_detail": {
"code": "VA_CREATION_DECLINED",
"message": "zerohash could not open this virtual account for the account holder. Contact zerohash support for next steps.",
"at": "2026-08-20T14:32:01Z"
},
"created_at": "2026-08-20T14:32:01Z",
"updated_at": "2026-08-20T14:32:01Z"
}Failed - Creation failed
{
"virtual_account_id": "47d7dfe4-fcae-45fe-9242-7e14795f449c",
"participant_code": "CUST01",
"platform_code": "PLAT01",
"provider_type": "CRB",
"status": "failed",
"status_detail": {
"code": "VA_CREATION_FAILED",
"message": "zerohash was unable to open this virtual account. Contact zerohash support to request a new account.",
"at": "2026-08-20T14:32:01Z"
},
"created_at": "2026-08-20T14:32:01Z",
"updated_at": "2026-08-20T14:32:01Z"
}Virtual Account Deposit Events
Subscribe to this event by setting the x-zh-hook-payload-type header value to virtual_account_deposit.status_changed.
A virtual_account_deposit.status_changed webhook is emitted every time a deposit into a virtual account transitions between states - for example, when funds are received, when a deposit is held for review, or when a payout to the configured external account or wallet completes.
Virtual Account Deposit Statuses
The status field communicates the current stage of the deposit.
| Status | Description |
|---|---|
deposit_received | Funds landed at partner bank, but have not been credit yet. |
deposit_pending | Deposit is pending further processing or review before it can be cleared. |
deposit_cleared | Review cleared and the deposit was credited to the participant. |
deposit_rejected | Deposit was rejected and will be returned to the originator. Terminal. |
deposit_blocked | Confirmed sanctions match; funds quarantined. Terminal. |
payout_initiated | Conversion and payout to the configured external account or wallet has begun (only for AUTO_CONVERT_AND_WITHDRAW policy). |
payout_completed | Conversion and payout completed successfully (only for AUTO_CONVERT_AND_WITHDRAW policy). Terminal. |
payout_failed | The conversion or payout leg failed (only for AUTO_CONVERT_AND_WITHDRAW policy). Terminal. |
Virtual Account Deposit Payload Schema
| Field | Type | Description |
|---|---|---|
type | string | Always DEPOSIT. |
status | string | Current deposit status. See Virtual Account Deposit Statuses (table above) for possible values. |
transaction_id | string | Unique identifier for this deposit. Matches deposit_reference_id on the corresponding account_balance.changed movement. |
virtual_account_id | string | The virtual account the deposit landed in. |
participant_code | string | End customer's participant code. |
platform_code | string | Platform the account belongs to. |
amount | string | Deposit amount. Absent on payout_* statuses. |
currency | string | Deposit currency. Absent on payout_* statuses. |
rail | string | Funding rail, e.g. wire, ach, rtp. (Note: rtp is inclusive of FedNow) |
reference | string | Platform-supplied reference, if provided. |
reason | string | Single reason code. Present on deposit_pending, deposit_rejected, and deposit_blocked. |
reasons | string[] | Every hold cause, when more than one applies. Present only on some deposit_pending events; reason is still set alongside it. |
originator_name | string | Name on the originating account. |
settlement_policy_type | string | Present only on payout_initiated. |
external_account_id | string | Payout destination account id. Present on all payout_* statuses. |
asset | string | Payout asset (e.g. USDC.SOL). Present on all payout_* statuses. |
quantity | string | Settled payout amount in asset units. Present only on payout_completed. |
created_at | timestamp | Timestamp of this event. On payout_completed specifically, this is the settlement time, not the deposit's original creation time. |
virtual_account_deposit.status_changed Payload Examples
virtual_account_deposit.status_changed Payload ExamplesDeposit Received
Fires when a deposit lands at bank partner and is pending screening before being credited to customer.
{
"virtual_account_id": "47d7dfe4-fcae-45fe-9242-7e14795f449c",
"participant_code": "CUST01",
"platform_code": "PLAT01",
"transaction_id": "vat_3d9f6b2c1a8e4f7d",
"type": "DEPOSIT",
"status": "deposit_received",
"amount": "2500.00",
"currency": "USD",
"originator_name": "Leslie Barbara Knope",
"rail": "wire",
"reference": "INV-10432",
"created_at": "2026-08-25T09:10:44Z"
}
Note:For Virtual Accounts with the
HOLDsettlement policy, subscribe toaccount_balance.changedto see when USD deposits are credited to customer balances. To match a deposit event to its balance movement:
transaction_idon this event equalsdeposit_reference_idon the balance movementaccount_groupis your platform code,account_labelis the virtual account's label,account_typeisavailablerun_typeandmovement_typeare bothdepositSee examples on Account Balance.
Deposit Pending
Fires when an incoming deposit is being held for manual review. This can happen because of a name mismatch on a wire, or if a deposit is above configured limits. reason is over_threshold | name_mismatch | other. When more than one cause applies, reasons lists all of them.
{
"type": "DEPOSIT",
"status": "deposit_pending",
"transaction_id": "a790d94a-b6f0-42c0-9bfd-7065e4c7b7e9",
"virtual_account_id": "b6778167-7a81-49c4-b5ae-e81e701173a9",
"participant_code": "PART001",
"platform_code": "PLAT001",
"amount": "500.00",
"currency": "USD",
"rail": "wire",
"reference": "INV-2026-00417",
"reason": "over_threshold",
"reasons": ["over_threshold"],
"originator_name": "Acme Corp Treasury",
"created_at": "2026-09-14T15:06:12Z"
}Deposit Cleared
Fires when an incoming deposit has been approved during screening and will be credited to the end customer.
{
"type": "DEPOSIT",
"status": "deposit_cleared",
"transaction_id": "a790d94a-b6f0-42c0-9bfd-7065e4c7b7e9",
"virtual_account_id": "b6778167-7a81-49c4-b5ae-e81e701173a9",
"participant_code": "PART001",
"platform_code": "PLAT001",
"amount": "500.00",
"currency": "USD",
"rail": "wire",
"reference": "INV-2026-00417",
"originator_name": "Acme Corp Treasury",
"created_at": "2026-09-14T15:13:02Z"
}Deposit Rejected
Fires when an incoming deposit will not be credited to the end customer, but funds are designated for return to the originator. Where "reason" can be = JURISDICTION_DENIED | NAME_MISMATCH
{
"type": "DEPOSIT",
"status": "deposit_rejected",
"transaction_id": "a790d94a-b6f0-42c0-9bfd-7065e4c7b7e9",
"virtual_account_id": "b6778167-7a81-49c4-b5ae-e81e701173a9",
"participant_code": "PART001",
"platform_code": "PLAT001",
"amount": "500.00",
"currency": "USD",
"rail": "wire",
"reference": "INV-2026-00417",
"reason": "JURISDICTION_DENIED",
"originator_name": "Acme Corp Treasury",
"created_at": "2026-09-14T15:10:47Z"
}Deposit Blocked
Fires when an incoming deposit will not be credited to the end customer, and funds will NOT be returned to originator due to sanctions screening results.
{
"type": "DEPOSIT",
"status": "deposit_blocked",
"transaction_id": "a790d94a-b6f0-42c0-9bfd-7065e4c7b7e9",
"virtual_account_id": "b6778167-7a81-49c4-b5ae-e81e701173a9",
"participant_code": "PART001",
"platform_code": "PLAT001",
"amount": "500.00",
"currency": "USD",
"rail": "wire",
"reference": "INV-2026-00417",
"reason": "SANCTIONS_MATCH",
"originator_name": "Acme Corp Treasury",
"created_at": "2026-09-14T15:12:30Z"
}Payout Initiated
Fires when an incoming deposit begins automatic conversion and payout to the account's configured external account. There is still a preceding deposit_received event for this settlement policy. This only applies to accounts with the settlement policy AUTO_CONVERT_AND_WITHDRAW.
{
"virtual_account_id": "47d7dfe4-fcae-45fe-9242-7e14795f449c",
"participant_code": "CUST01",
"platform_code": "PLAT01",
"transaction_id": "vat_9c4e1f7a2b6d3e8f",
"type": "DEPOSIT",
"originator_name": "Leslie Barbara Knope",
"rail": "wire",
"status": "payout_initiated",
"external_account_id": "107e8a2a-c835-4b76-b49d-a633d45727b9",
"asset": "USDC.SOL",
"reference": "INV-10432",
"created_at": "2026-08-25T09:10:44Z"
}Payout Completed
Fires once the conversion and payout have completed successfully. This only applies to accounts with the settlement policy AUTO_CONVERT_AND_WITHDRAW. created_at on this event is the settlement timestamp, not the deposit's original creation time.
{
"virtual_account_id": "47d7dfe4-fcae-45fe-9242-7e14795f449c",
"participant_code": "CUST01",
"platform_code": "PLAT01",
"transaction_id": "vat_9c4e1f7a2b6d3e8f",
"type": "DEPOSIT",
"status": "payout_completed",
"external_account_id": "107e8a2a-c835-4b76-b49d-a633d45727b9",
"asset": "USDC.SOL",
"quantity": "2499.7",
"originator_name": "Leslie Barbara Knope",
"rail": "wire",
"reference": "INV-10432",
"created_at": "2026-08-25T09:12:03Z"
}Payout Failed
Fires if the conversion or payout leg fails. This only applies to accounts with the settlement policy AUTO_CONVERT_AND_WITHDRAW.
{
"virtual_account_id": "47d7dfe4-fcae-45fe-9242-7e14795f449c",
"participant_code": "CUST01",
"platform_code": "PLAT01",
"transaction_id": "vat_9c4e1f7a2b6d3e8f",
"type": "DEPOSIT",
"originator_name": "Leslie Barbara Knope",
"rail": "wire",
"status": "payout_failed",
"external_account_id": "107e8a2a-c835-4b76-b49d-a633d45727b9",
"asset": "USDC.SOL",
"reference": "INV-10432",
"created_at": "2026-08-25T09:10:44Z"
}