Unauthorized deposits - Recovery
To prevent unauthorized deposits initiated outside the Auth flow, please follow the recovery steps outlined below.
Introduction
This guide outlines how to identify and recover unauthorized deposits that are initiated outside of the standard zerohash Auth flow. While such cases are rare, it's important for platforms to detect and handle them gracefully to maintain a secure and consistent user experience. The following sections describe the end-to-end flow, from detection to user resolution.
Recovery steps
1. Unauthorized deposit received
A deposit is made that bypasses the intended Auth flow.
2. Webhook triggered
zerohash sends a DEPOSIT_FAILED
webhook event to the platform, notifying it of the incoming deposit.
3. User authentication via Platform SSO
The user signs into using the Platform's login flow using normal SSO credentials.
4. User notification
The Platform, on their UI, will display an in-app banner or create some call to action that a resolution is required. It is also recommended to send an email explaining the resolution process.
Once the action has been taken, the Platform should invoke the Auth SDK - allowing the user to link an external exchange or wallet account and initiate a withdraw.

Example front end - see call-to-action banner in the center
5. User initiates withdrawal
The user will successfully sign into their custodial or non-custodial account to initiate the withdrawal. Zerohash will also allow for a "Manual Transfer" withdraw method as well. Note: zerohash will only permit withdrawals of the same asset that they deposited - no exchanges.

Withdraw method selection screen
Technical integration + front end journey
1. Webhook - Unauthorized deposit detected
When zerohash detects that a deposit was sent to an address outside of the auth flow, we will send a DEPOSIT_FAILED
webhooks. Example payload:
{
"participant_code": "<PARTICIPANT_CODE>",
"asset": "USDC.BASE",
"quantity": "100",
"transaction_id": "a07407e8f98c21b037b4aa0cbc852b8489c5e122fcc3d4b33b7827d0605ad8ff",
"success": false
}
2. Invoke Auth SDK
To generate a JWT token for the unauthorized deposit-specific flow, include the following parameters in the POST /client_auth_token
. request body:
"permission": "auth"
- indicates the request is for the Auth SDK"recovery": "true"
- enables support for the recovery flow
Example call:
{
"participant_code": "<PARTICIPANT_CODE_HERE>",
"permissions": ["auth"],
"recovery": "true"
}
From there, you can invoke the SDK. Example using React:
import React from 'react';
import ZeroHashSDK, { AppIdentifier } from 'zh-web-sdk';
const App = () => {
const sdk = new ZeroHashSDK({
zeroHashAppsURL: "<https://web-sdk.cert.zerohash.com">, //prod: <https://web-sdk.zerohash.com>
authJWT: "\<JWT_TOKEN_HERE>"
});
sdk.openModal({ appIdentifier: AppIdentifier.AUTH })
return \<>\</>;
}
export default App;
3. Screen 1 - Display all unauthorized deposits

This screen will display a line item-per unauthorized deposit. It will contain important data points such as blockchain transaction hash, timestamp of receipt, asset received, network received on, and amount.
From there, the user will select each transaction to resolve and withdraw out to an external wallet.
4. Screen 2 - Select withdraw method

The user can choose from a Custodial account, non-custodial account or can choose to transfer manually by entering their address manually
5. Screens 3 - 9 Custodial or Non-custodial account

The user will be prompted to:
- Sign into their account
- Perform 2FA
- Confirm the details of their transfer
- Perform 2FA once more
- Ultimately initiate the transfer
6. Screens 3 - 7 Transfer Manually
The user will be prompted to:
- Enter the destination address
- zerohash will screen the address against our transaction monitoring deny-lists, ensuring funds are not sent to tainted addresses
- Initiate the transfer
7. Webhook - Transfer initiated
When the transfer has been sent on-chain, zerohash will send a series of webhooks that lets the Platform understand the progress of the transfer
submitted
example - the transfer has started:
{
"payment_details":{
"withdrawal_request_id":"b752503c-1c42-4dfe-ad1d-7b39da5db59c",
"trade_id":"",
"on_chain_transaction_id":"",
"network_fee_notional":"",
"network_fee_quantity":"",
"destination_address":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
},
"asset":"USDC",
"network":"BASE",
"payment_type":"auth_withdrawal",
"external_account_id":"2cc93b20-ee43-4877-8cdc-863e61829015",
"participant_code":"<PARTICIPANT_CODE>",
"quantity":"",
"status":"submitted",
"created_at":"2024-09-26T13:05:22.657Z",
"updated_at":"2024-09-26T13:05:22.657Z",
"total":"100.00
}
posted
example - the transfer is officially pending on-chain and is on its way to the external wallet:
{
"payment_details":{
"withdrawal_request_id":"b752503c-1c42-4dfe-ad1d-7b39da5db59c",
"trade_id":"",
"on_chain_transaction_id":"FLaUcxdNxRwnaSXp6pXeRSfANXEHouqYbqF6X1bgRxg2",
"network_fee_notional":".01",
"network_fee_quantity":".001",
"destination_address":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
},
"asset":"USDC",
"network":"BASE",
"payment_type":"auth_withdrawal",
"external_account_id":"2cc93b20-ee43-4877-8cdc-863e61829015",
"participant_code":"<PARTICIPANT_CODE>",
"quantity":"",
"status":"posted",
"created_at":"2024-09-26T13:05:22.657Z",
"updated_at":"2024-09-26T13:05:22.657Z",
"total":"100.00
}
settled
example - the transfer is complete and the user has their funds:
{
"payment_details":{
"withdrawal_request_id":"b752503c-1c42-4dfe-ad1d-7b39da5db59c",
"trade_id":"",
"on_chain_transaction_id":"FLaUcxdNxRwnaSXp6pXeRSfANXEHouqYbqF6X1bgRxg2",
"network_fee_notional":".01",
"network_fee_quantity":".001",
"destination_address":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
},
"asset":"USDC",
"network":"BASE",
"payment_type":"auth_withdrawal",
"external_account_id":"2cc93b20-ee43-4877-8cdc-863e61829015",
"participant_code":"<PARTICIPANT_CODE>",
"quantity":"",
"status":"settled",
"created_at":"2024-09-26T13:05:22.657Z",
"updated_at":"2024-09-26T13:05:22.657Z",
"total":"100.00
}
Updated 1 day ago