Changelog

Webhook payloads (October 2026)

The webhook envelope, the events no longer sent, and every payload field that changed, with what your integration must update.

Release: webhook payloads · Date: 2026-10-14 · Environment: sandbox first, then production

Corrected on 2026-10-15

Some payloads described here were corrected afterwards: the resulting balances, network codes, treasury amounts, sweep_tx_hash, and the new user.funds.released. Read Webhook payload fixes (2026-10-15) for what changed since this page.

This release puts every webhook on one documented catalogue. Each event now has a fixed data shape, closed reason_code values and a resource it's ordered by. Fields that exposed internal routing or ids are gone. The full catalogue is in Webhooks.

These changes take effect as soon as the release reaches your environment. Webhook payloads have no deprecated aliases.

Before the release reaches your environment

Check these first. They break integrations that don't change:

  1. Read event_type. The top-level event key is gone.
  2. Order events per resource with sequence_number, not per account.
  3. Stop waiting for cosigner.rejected, withdrawal.queued, withdrawal.queue.processed, user.wallet.address_created and user.deposit.return_initiated.
  4. Read withdrawal ids from withdrawal_id. atx_id and withdraw_id are gone.
  5. Read reason_code, not reason, on treasury.withdrawal.failed and treasury.batch.failed.
  6. Don't read email from user.withdrawal.verification_code_issued.

Envelope

ChangeBeforeAfterWhat you must change
Event nameSent twice, as event and event_typeOnly event_typeRead event_type.
sequence_numberOne counter per customer accountOne counter per resource (resource_type + resource_id), starting at 1Store the last number per resource, not per account. Compare numbers only between events of the same resource.
Body keysevent plus the envelopeExactly event_id, event_type, occurred_at, resource_type, resource_id, sequence_number, dataNothing else to read at the top level.

Resources that already had events keep counting from their highest number. A new resource starts at 1.

Resource changes

Some events now use a different resource. Their sequence_number follows the new resource.

EventBeforeAfter
ledger.wallet.debited for a withdrawalwithdrawal, the withdrawal idwallet, the wallet id
ledger.wallet.debited and ledger.wallet.credited for a transfertransfer, the transfer idwallet, the wallet id
ledger.wallet.credited with reason withdrawal_reversalwithdrawal, the withdrawal idwallet, the wallet id
custody.withdrawal.requestedwithdrawal, an internal custody idwithdrawal, the public withdrawal id
user.wallet.createdwallet, an internal custody wallet idwallet, the wallet id the API returns
gas.primary.threshold_breached, gas.backup.activated, gas.backup.threshold_breachedwallet, the gas wallet idgas, primary or backup

Events no longer sent

EventListen to instead
cosigner.rejectedThe outcome of the refused transaction, with reason_code: policy_rejected: treasury.withdrawal.failed for a withdrawal, treasury.batch.failed for a batch, user.deposit.failed for a deposit. Other refusals send no event.
withdrawal.queuedcustody.withdrawal.requested, which is sent for the same withdrawal.
withdrawal.queue.processedThe events of each withdrawal (custody.withdrawal.executed, treasury.withdrawal.failed). Read the withdrawal for its status.
user.wallet.address_createdThe addresses list in user.wallet.created.
user.deposit.return_initiatedledger.wallet.reversed when the return starts, and user.deposit.returned when it settles.

Some cases of existing events are no longer sent either:

CaseBeforeAfter
High-risk deposituser.deposit.screening_failed and user.deposit.failed with high_risk_kytOnly user.deposit.screening_failed.
Blocked deposituser.deposit.screening_failed and user.deposit.failed, both with blacklisted_addressuser.deposit.screening_failed with blacklisted, and user.funds.frozen with screening_failed.
Deposit that can't be screeneduser.deposit.screening_failed or user.deposit.failed with screening_unavailableNo event. The deposit stays held for manual review.
Treasury deposit without a screening resulttreasury.deposit.screened with risk_result: "unknown"No event.
Wallets that aren't your users' walletsuser.wallet.created for every new wallet, including treasury walletsOnly for your users' wallets.

Field changes by event

"Removed" fields are no longer sent. Ignore them if your code still expects them.

Verification codes

EventBeforeAfterWhat you must update
user.withdrawal.verification_code_issuedCarried email when the code arrived by emailNo email. Adds attempts_remaining (3) when the treasury pays directly.Don't read email. Read attempts_remaining when present.

Deposits

EventBeforeAfterWhat you must update
user.deposit.screening_failedreason_code: high_risk or elevated_risk by risk band, blacklisted_address, screening_unavailablereason_code: elevated_risk (medium risk), high_risk, severe_risk, blacklistedMap the new values. Severe risk now has its own value.
user.deposit.failedSent for screening failures, with wallet_id, deposit_id, reason_code, tx_hashSent only when a signing policy refuses the deposit's transaction: deposit_id and reason_code: policy_rejectedHandle it as a stuck deposit, not a screening result.
user.deposit.swept_to_treasury (the event with batch_id)deposit_id, batch_id, forward_tx_name, tx_hash, total_amount, treasury_share, treasury_amount, forward_amount, currency, destination_address, deposit_tx_hash, depositwallet_id, deposit_id, sweep_tx_hash, destination, batch_idRead the transaction from sweep_tx_hash. Read amounts from the deposit.

Wallets

EventBeforeAfterWhat you must update
user.wallet.createdwallet_id was an internal custody wallet id; wallet_type always virtual; no addresseswallet_id is the API wallet id; wallet_type is virtual or real; adds addresses with address_id, currency, network, addressMatch wallets by the API id. Read the deposit addresses from addresses.

Withdrawals

EventBeforeAfterWhat you must update
treasury.withdrawal.requestedatx_id, routing, currency, amount, destination_address, user_id, pending_id (fiat: withdrawal_type, no address)withdrawal_id, amount, currency, address (empty for fiat)Read withdrawal_id and address.
treasury.withdrawal.approvedatx_id, routing, user_idwithdrawal_idRead withdrawal_id.
treasury.withdrawal.executedatx_id, routing, currency, amount, destination_address, user_id, tx_hashwithdrawal_id, amount, currency, address, tx_hashRead withdrawal_id and address.
treasury.withdrawal.failedatx_id, user_id, reason (cancel also routing, currency, amount)withdrawal_id, reason_codeRead withdrawal_id and reason_code. policy_rejected is a new value.
custody.withdrawal.requestedatx_id (an internal custody id), safe_request_id, pending_id, currency, amount, treasury_wallet_address, final_destination_address, user_idwithdrawal_id (the public id), amount, currencyRead withdrawal_id. Read the destination from the withdrawal.
custody.withdrawal.executedwithdraw_id, safe_request_id, pending_withdrawal_id, currency, amount, treasury_wallet_address, final_destination_address, user_id, tx_status, tx_hashwithdrawal_id, amount, currency, tx_hashRead withdrawal_id. Read the status from the withdrawal.

Ledger

EventBeforeAfterWhat you must update
ledger.wallet.debited for a withdrawalOne event per withdrawal: withdraw_id, user_id, amount, asset, destination_address, reason, note, tx_hash, and the user's total balancesOne event per wallet that paid: wallet_id, amount (that wallet's share), currency, reason: withdrawal, and that wallet's balancesSum the events of one withdrawal per wallet. Read currency, not asset. Match the withdrawal through its debited_wallets.
ledger.wallet.debited and ledger.wallet.credited for a transferavailable, held and balance as JSON numbersDecimal stringsParse balances as decimal strings.
ledger.wallet.credited with reason withdrawal_reversalwithdraw_id, user_id, note, plus the common fieldswithdrawal_id plus the common fieldsRead withdrawal_id.

Treasury

EventBeforeAfterWhat you must update
treasury.batch.executedbatch_id, vault_id, asset, deposit_count, transfers, transfer_summaries, deposits, tx_hashesbatch_id, amount, currency, transactions (tx_hash, amount, currency)Read the total from amount and the hashes from transactions.
treasury.batch.failedbatch_id, vault_id, asset, deposit_count, reason, failed_count, sometimes succeeded_count, deposits, error, currencybatch_id, reason_code, succeeded_count, failed_countRead reason_code. policy_rejected is a new value.
treasury.deposit.screenedrisk_result: a risk band or unknownrisk_result: low, medium, high or severeMap the new values.
treasury.wallet.flaggedreason_code: contamination_detectedreason_code: contaminationMap the new value.
treasury.wallet.createdwallet_id, vault_id, previous_wallet_id, reason, triggered_by_transaction_idwallet_id, addressesRead the old wallet from treasury.wallet.rotated.
treasury.funds.frozenAlso wallet_id and noteamount, currency, reason_code, scope, hold_idRead the wallet from resource_id.
treasury.funds.releasedreason_code: wallet_rotated_after_contamination, plus wallet_id and new_wallet_idreason_code: contamination, no wallet idsMatch it to the frozen event by hold_id.
treasury.contamination.remediatedAlso new_wallet_id, sweep_results, total_swept, total_failed; sweep_tx_hash absent when nothing movedwallet_id, amount, currency, sweep_tx_hash (empty when nothing moved)Read the new wallet from treasury.wallet.rotated.
treasury.wallet.rotatedwallet_id, new_wallet_id, triggering_transaction_id, contaminated_asset, swept_assetswallet_id, new_wallet_id, new_addressesRead the new addresses from new_addresses.

Gas

EventBeforeAfterWhat you must update
gas.primary.threshold_breachedsponsor_wallet_id, vault_id, balance, threshold, contextsource: primary, balance, threshold, currency, remaining_transactions (when estimated)Read currency and remaining_transactions.
gas.backup.threshold_breachedbackup_gas_wallet_id, vault_id, balance, threshold (when set), contextsource: backup, balance, threshold, currency, remaining_transactions (when estimated)Same as above.
gas.backup.activatedsponsor_wallet_id, vault_id, balance, contextreason: primary_depletedRead reason.

Unchanged

These events keep their payload: user.transfer.verification_code_issued, user.deposit.received, user.deposit.screening_passed, user.deposit.returned, user.deposit.suspended_address_received, custody.deposit.confirmed, ledger.wallet.added, ledger.wallet.credited with reason screening_cleared, ledger.wallet.reversed, ledger.transfer.failed, treasury.deposit.received, and user.deposit.swept_to_treasury without batch_id. Only the envelope changes for them.

On this page