Webhook payload fixes (2026-10-15)
Corrections to the October 2026 webhook payloads: one balance rule for every ledger and funds event, canonical network codes, real amounts, on-chain hashes only, the new user.funds.released event and the end of the legacy code body.
Release: webhook payload fixes · Date: 2026-10-15 · Environment: sandbox first, then production
This update corrects payloads of the October 2026 webhook release. Several fields carried values that the catalogue didn't promise: provider network names, zero amounts, internal transaction names and user totals instead of wallet balances. They now carry what Webhooks documents. It also adds user.funds.released and removes the legacy code notification body.
These changes take effect as soon as the update reaches your environment. The envelope doesn't change.
Before the update reaches your environment
Check these first. They break integrations that don't change:
- Read balances on
ledger.wallet.*anduser.funds.frozenas the balances of the wallet in the event, not the user's totals.heldno longer includes deposits pending screening. - Accept
nullinsweep_tx_hash,addresses[].network,new_addresses[].networkand theamountof treasury holds and oftreasury.contamination.remediated. - Stop reading the withdrawal code from the legacy
{"pin", …}body. Read it fromuser.withdrawal.verification_code_issued.
Removed: the legacy code notification
| Before | After | What you must change |
|---|---|---|
When a withdrawal code reached Sovera by email, your endpoint also received an unsigned body without the envelope: {"pin", "email", "atx_id", "amount", "currency", "destination_address"}. | Not sent. user.withdrawal.verification_code_issued is the only delivery of the code. | Read the code from the event. Your endpoint now receives only events and the test notification of Verify Webhook. |
New event: user.funds.released
Sent when the review of a deposit frozen by user.funds.frozen approves it. The frozen amount becomes available in the same wallet, once, and ledger.wallet.credited with reason screening_cleared follows with the same balances. Resource: wallet, and resource_id is the wallet id.
{
"wallet_id": "fd956820-0446-5e40-a0fe-8fff794b7fd6",
"deposit_id": "96abb315-99d8-59e7-a333-1007dba37ad5",
"amount": "150.00000000",
"currency": "usdc",
"reason_code": "screening_failed",
"status": "ACTIVE",
"hold_id": "hold_deposit_96abb315-99d8-59e7-a333-1007dba37ad5",
"available": "470.00000000",
"held": "0.00000000",
"balance": "470.00000000"
}deposit_id and hold_id are the same values as on user.funds.frozen. A rejected deposit stays frozen and sends no event. A hold announced before this update can't be released this way: approving that deposit moves no funds and sends no event. See user.funds.released.
Field changes by event
Resulting balances
| Event | Before | After | What you must update |
|---|---|---|---|
ledger.wallet.added, ledger.wallet.credited (screening_cleared, transfer, withdrawal_reversal), ledger.wallet.debited (transfer), ledger.wallet.reversed, user.funds.frozen | The user's totals for the currency across all their wallets, with held = the amount pending screening and balance = available + that amount | The balances of the wallet named in the event: its available, its held (funds reserved for an operation that hasn't run yet) and balance = available + held. Exact decimal strings with at least 8 decimals. | Apply the balances to the wallet in wallet_id, not to the user. Don't expect a pending deposit in held or balance. |
ledger.wallet.debited (withdrawal) | Already that wallet's balances | Unchanged | Nothing. |
A deposit still pending screening, or frozen by user.funds.frozen, isn't part of available, held or balance. So ledger.wallet.added shows the wallet's balances without the new deposit, and user.funds.frozen no longer shows the frozen amount in held. On ledger.wallet.credited with reason withdrawal_reversal, the balances now have 8 decimals; amount keeps the share as recorded and can have fewer. See Resulting balances.
Network codes
| Event | Before | After | What you must update |
|---|---|---|---|
user.wallet.created (addresses[].network) | The custody provider's network name (networks/ethereum-testnet-sepolia), or "" when missing | The canonical network code (ETHEREUM, TRON, …), the same value List Deposit Addresses returns. null when the network is unknown or missing. | Match networks by canonical code. Handle null. |
treasury.wallet.created (addresses[].network), treasury.wallet.rotated (new_addresses[].network) | The custody provider's network name | The canonical network code, or null when unknown | Same as above. |
Treasury amounts
| Event | Before | After | What you must update |
|---|---|---|---|
treasury.funds.frozen, treasury.funds.released (amount) | Always 0.00000000 | The amount of the contaminated deposit the hold covers, the same amount treasury.deposit.received reported. null if the deposit carried no amount. | Read the amount from the event. |
treasury.contamination.remediated (amount) | Always 0.00000000 | The balance of the contaminated currency left isolated in the old wallet, read when the clean funds are moved out. Else the contaminated deposit amount. null if neither is known. | Read the amount from the event. Handle null. |
Transaction hashes
| Event | Before | After | What you must update |
|---|---|---|---|
user.deposit.swept_to_treasury (without batch_id) | An internal custody transaction name, not a hash | Always null: when the sweep starts, its on-chain hash isn't known yet | Don't expect a hash on this event. |
user.deposit.swept_to_treasury (with batch_id) | The hash, else the last part of an internal transaction name | The hash, else null | Handle null. |
treasury.contamination.remediated (sweep_tx_hash) | The internal name of the first sweep, else "" | The on-chain hash of the first sweep, else null | Handle null instead of "". |
No event sends the hash again once the transaction confirms.
Withdrawal Workflow
End-to-end guide to crypto withdrawals: initiate on a network, receive the verification code by webhook, confirm, and track the withdrawal until it settles on-chain.
Webhook payloads (October 2026)
The webhook envelope, the events no longer sent, and every payload field that changed, with what your integration must update.