An opt-in variant of the account balance webhook that embeds the trade, deposit, withdrawal or transfer that caused the balance to change
enriched_account_balance.changed carries the same balance information as Account Balance update, plus details of the source event that triggered it. Instead of receiving a balance change and then querying the API to find out what caused it, you receive both in one payload.
Note: the x-zh-hook-payload-type header is set to enriched_account_balance.changed.
This webhook is opt-in and independent
enriched_account_balance.changedis enabled per platform and operates independently ofaccount_balance.changed. Enabling it does not change or disable the existing balance webhook. We recommend subscribing to one or the other rather than both, since every balance change would otherwise reach you twice in two different shapes.To enable it, contact zerohash through your platform Slack channel or your relationship manager. Enrichment must be switched on for your account group before the subscription will produce any events.
What you receive
Where account_balance.changed emits one event per balance change, the enriched webhook splits that change into one event per source movement. A single order that fills against two resting orders settles as two trades, and you receive two enriched webhooks: distinct movements[0].trade_id and trades[0].execution_id, the same trades[0].order.client_order_id.
balancehas a different meaning hereIn
account_balance.changed,balanceis the account balance after the whole event. Inenriched_account_balance.changed,account.balanceis the running balance after this movement group only. When several movements come from one event, each webhook reports the balance as of its own movement, so the final balance is the one on the last webhook you receive.The key name is the same and the meaning is not. If you are adapting an existing
account_balance.changedparser, this is the field to check first.
Today only trade-sourced movements carry source detail. Deposits, withdrawals and transfers are delivered in the same enriched shape but without a trades array, so they are equivalent in content to the existing balance webhook.
Payload structure
Top level
| Field | Type | Description |
|---|---|---|
account | object | Account identity and the running balance. See below. |
movements | array | The itemized movements in this group. See below. |
trades | array | The resolved trade behind this movement. Omitted entirely when the movement is not trade-sourced. |
direction | string | credit or debit, from the sign of the net change for this account. |
enrichment_status | string | Whether source detail was resolved. See Enrichment status. |
run_id | string | Identifier of the run that produced the movement. |
run_type | string | settlement, deposit, withdrawal or transfer. |
timestamp | number | Unix milliseconds. Use this to order events, not the order of delivery. |
zrn | string | The account's zerohash resource name. Omitted when it cannot be resolved; the webhook still fires. |
account
| Field | Type | Description |
|---|---|---|
participant_code | string | The participant the account belongs to. |
account_group | string | The account group. |
account_label | string | The account label. |
account_type | string | available or collateral. |
asset | string | The asset whose balance changed. |
balance | string | Running balance after this movement group. See the note above. |
movements
| Field | Type | Description |
|---|---|---|
movement_id | string | Unique identifier of the movement. |
account_id | string | The account the movement was applied to. |
movement_type | string | For example final_settlement, deposit, withdrawal_pending, transfer. |
movement_timestamp | number | Unix milliseconds. |
change | string | Signed change applied to the balance. |
trade_id | string | Present for trade-sourced movements. |
parent_link_id | string | Present when the movement links to a parent record. |
deposit_reference_id | string | Present for deposits. |
withdrawal_request_id | string | Present for withdrawals. |
transfer_request_id | string | Present for transfers. |
transfer_type | string | Present for transfers. |
trades
Present only for trade-sourced movements, and today carries at most one element.
side, commission, commission_asset and execution_id are reported from your own leg of the trade, not as an array of both counterparties.
| Field | Type | Description |
|---|---|---|
symbol | string | The traded instrument, for example BTC/USD. |
side | string | buy or sell, from your side of the trade. |
trade_price | string | Execution price. |
trade_quantity | string | Executed quantity. |
total_notional | string | Gross trade amount. |
transaction_ts | number | Execution time, Unix milliseconds. |
commission | string | Commission on your leg. Central Limit Order Book trades only. |
commission_asset | string | Asset the commission is charged in. Central Limit Order Book trades only. |
execution_id | string | Per-fill identifier on your leg. Central Limit Order Book trades only. |
order | object | The originating order. Central Limit Order Book trades only. |
trades[].order
Attached to Central Limit Order Book trades only. Request For Quote trades are spread-priced and have no order, commission or execution id, so all four are absent.
| Field | Type | Description |
|---|---|---|
order_type | string | For example limit, market. |
time_in_force | string | For example good_till_cancel. |
ord_status | string | FIX OrdStatus, for example filled, partially_filled. This is how the order's state reads. |
limit_price | string | Limit price, when the order type carries one. |
stop_price | string | Stop price, when the order type carries one. |
expiry_time | number | Unix milliseconds, when set. |
client_order_id | string | Your own order identifier. Shared across every fill of the same order. |
trade_match_id | string | FIX TrdMatchID of the settled trade. Suitable as an idempotency key. |
Every field above except the ones marked as always present is omitted when it has no value, rather than sent as an empty string.
Enrichment status
enrichment_status reports whether source detail was resolved for the event.
| Value | Meaning |
|---|---|
ok | The event was built as expected. Every event emitted today carries this value. |
degraded | Reserved. Balance data is correct but some source detail could not be resolved. |
not_applicable | Reserved. No enrichment applies to the movement. |
unknown | Reserved. Treat as degraded. |
Read the field and log anything other than ok, but do not branch your integration on the reserved values yet. Balance fields are always authoritative regardless of status; only the source detail is best-effort.
Examples
Central Limit Order Book trade
{
"account": {
"account_group": "D62RM3",
"account_label": "label-3a-taker-140648",
"account_type": "available",
"asset": "BTC",
"balance": "0.0483",
"participant_code": "Y254LS"
},
"direction": "debit",
"enrichment_status": "ok",
"movements": [
{
"account_id": "26da431f-33ea-5d03-b87e-d7b4d6114dfa",
"change": "-0.00070000",
"movement_id": "f4b5d360-19cd-40db-89a6-5ad5a3cbf730",
"movement_timestamp": 1790931032489,
"movement_type": "final_settlement",
"parent_link_id": "aefa9ad5-385b-4b3e-9b20-bd0c3a61325e",
"trade_id": "aefa9ad5-385b-4b3e-9b20-bd0c3a61325e"
}
],
"run_id": "6250341",
"run_type": "settlement",
"timestamp": 1790931032642,
"trades": [
{
"commission": "0",
"commission_asset": "USD",
"execution_id": "CV7TVTZQP7RS",
"order": {
"client_order_id": "taker-1790931021",
"limit_price": "49000",
"ord_status": "partially_filled",
"order_type": "limit",
"time_in_force": "good_till_cancel",
"trade_match_id": "CV7TVTZQM7RS"
},
"side": "sell",
"symbol": "BTC/USD",
"total_notional": "34.30",
"trade_price": "49000",
"trade_quantity": "0.0007",
"transaction_ts": 1790931025796
}
],
"zrn": "zrn:zh:us:accounts:customer:ccbf84b9-e529-51d0-83f0-27dfb824472e"
}Request For Quote trade
No order block, commission or execution_id.
{
"account": {
"account_group": "D62RM3",
"account_label": "general",
"account_type": "available",
"asset": "BTC",
"balance": "0.00017336",
"participant_code": "Y254LS"
},
"direction": "credit",
"enrichment_status": "ok",
"movements": [
{
"account_id": "4fdb6e48-9986-5890-8c93-7526e2c381d8",
"change": "0.00017336",
"movement_id": "d1051362-8c36-4278-97db-dfbaca079ff0",
"movement_timestamp": 1790931597601,
"movement_type": "final_settlement",
"trade_id": "d571acf8-82ca-4bc1-881a-124b972f1383"
}
],
"run_id": "6250351",
"run_type": "settlement",
"timestamp": 1790931597769,
"trades": [
{
"side": "buy",
"symbol": "BTC/USD",
"total_notional": "15.00",
"trade_price": "86525.1499769266266728",
"trade_quantity": "0.00017336",
"transaction_ts": 1790931596926
}
]
}Deposit
Non-trade sources omit trades entirely. Withdrawals and transfers follow the same shape, with run_type withdrawal or transfer. A transfer produces one webhook per leg: a debit on the source account and a credit on the destination.
{
"account": {
"account_group": "D62RM3",
"account_label": "label-3a-taker-140648",
"account_type": "available",
"asset": "ETH",
"balance": "2.6",
"participant_code": "Y254LS"
},
"direction": "credit",
"enrichment_status": "ok",
"movements": [
{
"account_id": "d0e98c31-ddea-5382-ab46-3c75dc426e6b",
"change": "0.100000000000000000",
"deposit_reference_id": "8e4da068-fe05-4ca4-9c87-46dfd3e6c24c",
"movement_id": "433e8313-4470-4724-8135-dde315bec9e4",
"movement_timestamp": 1790931771603,
"movement_type": "deposit"
}
],
"run_id": "6250362",
"run_type": "deposit",
"timestamp": 1790931771626,
"zrn": "zrn:zh:us:accounts:customer:ccbf84b9-e529-51d0-83f0-27dfb824472e"
}Differences from account_balance.changed
| Aspect | account_balance.changed | enriched_account_balance.changed |
|---|---|---|
| Account identity | Flat at the top level | Nested under account |
balance | Balance after the whole event | Running balance after this movement group |
| Delivery | One event per balance change | One event per source movement, so several per multi-fill order |
| Source detail | None. movements[].trade_id only | trades[] with trade economics and, for CLOB, the order block |
| Extra fields | None | direction, enrichment_status, zrn |
| Subscription | On by default where configured | Opt-in, enabled per platform |
Retry policy, delivery headers, signature verification and ordering are unchanged. See Overview and Webhook Security.