Enriched Account Balance Update

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.changed is enabled per platform and operates independently of account_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.

❗️

balance has a different meaning here

In account_balance.changed, balance is the account balance after the whole event. In enriched_account_balance.changed, account.balance is 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.changed parser, 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

FieldTypeDescription
accountobjectAccount identity and the running balance. See below.
movementsarrayThe itemized movements in this group. See below.
tradesarrayThe resolved trade behind this movement. Omitted entirely when the movement is not trade-sourced.
directionstringcredit or debit, from the sign of the net change for this account.
enrichment_statusstringWhether source detail was resolved. See Enrichment status.
run_idstringIdentifier of the run that produced the movement.
run_typestringsettlement, deposit, withdrawal or transfer.
timestampnumberUnix milliseconds. Use this to order events, not the order of delivery.
zrnstringThe account's zerohash resource name. Omitted when it cannot be resolved; the webhook still fires.

account

FieldTypeDescription
participant_codestringThe participant the account belongs to.
account_groupstringThe account group.
account_labelstringThe account label.
account_typestringavailable or collateral.
assetstringThe asset whose balance changed.
balancestringRunning balance after this movement group. See the note above.

movements

FieldTypeDescription
movement_idstringUnique identifier of the movement.
account_idstringThe account the movement was applied to.
movement_typestringFor example final_settlement, deposit, withdrawal_pending, transfer.
movement_timestampnumberUnix milliseconds.
changestringSigned change applied to the balance.
trade_idstringPresent for trade-sourced movements.
parent_link_idstringPresent when the movement links to a parent record.
deposit_reference_idstringPresent for deposits.
withdrawal_request_idstringPresent for withdrawals.
transfer_request_idstringPresent for transfers.
transfer_typestringPresent 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.

FieldTypeDescription
symbolstringThe traded instrument, for example BTC/USD.
sidestringbuy or sell, from your side of the trade.
trade_pricestringExecution price.
trade_quantitystringExecuted quantity.
total_notionalstringGross trade amount.
transaction_tsnumberExecution time, Unix milliseconds.
commissionstringCommission on your leg. Central Limit Order Book trades only.
commission_assetstringAsset the commission is charged in. Central Limit Order Book trades only.
execution_idstringPer-fill identifier on your leg. Central Limit Order Book trades only.
orderobjectThe 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.

FieldTypeDescription
order_typestringFor example limit, market.
time_in_forcestringFor example good_till_cancel.
ord_statusstringFIX OrdStatus, for example filled, partially_filled. This is how the order's state reads.
limit_pricestringLimit price, when the order type carries one.
stop_pricestringStop price, when the order type carries one.
expiry_timenumberUnix milliseconds, when set.
client_order_idstringYour own order identifier. Shared across every fill of the same order.
trade_match_idstringFIX 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.

ValueMeaning
okThe event was built as expected. Every event emitted today carries this value.
degradedReserved. Balance data is correct but some source detail could not be resolved.
not_applicableReserved. No enrichment applies to the movement.
unknownReserved. 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

Aspectaccount_balance.changedenriched_account_balance.changed
Account identityFlat at the top levelNested under account
balanceBalance after the whole eventRunning balance after this movement group
DeliveryOne event per balance changeOne event per source movement, so several per multi-fill order
Source detailNone. movements[].trade_id onlytrades[] with trade economics and, for CLOB, the order block
Extra fieldsNonedirection, enrichment_status, zrn
SubscriptionOn by default where configuredOpt-in, enabled per platform

Retry policy, delivery headers, signature verification and ordering are unchanged. See Overview and Webhook Security.