Account Link + Withdraw SDK Integration Guide
Platforms can use the Account Link and Withdraw SDK in tandem to allow their Customers to link an external account and then subsequently withdraw funds to it.
General
The Account Link SDK is an embeddable widget that allows Customers to link an external wallet. On the SDK front end, the Customer will select/enter the following:
- Asset (ie, USDC)
- Network (ie, Ethereum or Solana)
- Address
- Wallet Nickname
The Crypto Withdraw SDK is also an embeddable widget that allows Customers to trigger the withdrawal to their linked crypto wallet.
Account Link Front End Validations
Each account link attempt is validated, by Zero Hash on the front end, in the following ways:
- The Customer-entered address must be valid according to the Asset and Network they entered. For example, a Bitcoin address would not be allowed to be linked if the Asset entered was USDC
- The Customer-entered address must pass our compliance checks. For example, if the address is flagged as OFAC-sanctioned, the link request would be rejected
Example
The high-level flow is the following:
- Fund Float Account
- Account Link SDK - Request Access Token
- Account Link SDK - Link Account
- Account Link SDK - Consume External Account Webhook
- Account Link SDK - Query External Accounts
- Crypto Withdraw SDK - Request Access Token
- Crypto Withdraw SDK - Initiate Withdrawal
- Crypto Withdraw SDK - Consume Payments Webhook
- Crypto Withdraw SDK - Query Payments
- Complete EOD Settlement
Context for this Example
From a use case perspective, we'll assume the Platform is a Prediction Market which allows their Customer to fund their using USDC, leveraging the Fund product. After Customers win their bet or otherwise wish to take funds off the platform to another location, the Prediction Market also allows Customers to withdraw their funds in the form of USDC, leveraging the Account Link and Crypto Withdraw SDK.
For Fund, from a technical Setup perspective, we'll assume that the Platform is under the Onboarding API + Fund SDK setup.
The Customer participant_code
will be CUST01 and the Platform's is PLAT01.
1. Fund Float Account
The first step is for the Platform to fund its fiat (ie, USD) float account. This will be used to fund subsequent withdrawal requests, since technically each withdrawal request will result in a purchase of the withdrawal asset followed by an immediate withdrawal. Here are the account details:
- Participant_code: 00SCXM
- Account_group: PLAT01
- Account_label: general
- Account_type: available
- Asset: [fiat currency] (ie, USD)
NOTE: After each trade session, the Platform will be required to "top up" their float account for the amount that was cumulatively withdrawn. See section 10. Complete EOD Settlement for details.
2. Account Link SDK - Request Access Token
The Platform should request an access token specifying:
Field | Description | Example Value | Required? |
---|---|---|---|
participant_code | The 6-digit alpha numeric code associated with the Customer that is looking to withdraw (the response of the original POST /participants/customers/new call) | CUST01 | Y |
permissions | The array of permissions that will be granted to the returned JWT token | ["crypto-acccount-link"] | Y |
Example POST /client_auth_token
request:
{
"participant_code": "CUST01",
"permissions": ["crypto-account-link"]
}
After a successful POST /client_auth_token
call, the next step is to start the Account Link SDK
flow, see the example below for doing it in a React
application. Keep in mind that if you're using a Native Mobile App (Swift, Flutter, etc) instead of using zh-web-sdk
you should follow the WebView
approach, described here
import React from 'react';
import ZeroHashSDK, { AppIdentifier } from 'zh-web-sdk';
const App = () => {
const sdk = new ZeroHashSDK({
zeroHashAppsURL: "https://web-sdk.cert.zerohash.com",
cryptoAccountLinkJWT: "<JWT_TOKEN_HERE>"
});
sdk.openModal({
appIdentifier: AppIdentifier.CRYPTO_ACCOUNT_LINK, // "crypto-account-link"
})
return <></>;
}
export default App;
the Customer will be presented with the Account Link SDK. Assuming they select/enter the information successfully, the Platform will receive a webhook:
3. Account Link SDK - Link Account
At this point, the Customer is interacting with the front end SDK. The Customer will ultimately link their account.
Once the Customer successfully creates an Account we'll send a postMessage
event to inform the Platform that a new Account has been created and will be returned at GET /payments/external_accounts. The event type
will be CRYPTO_ACCOUNT_LINK_EXTERNAL_ACCOUNT_CREATED
.
[front end designs of the SDK will be placed here when ready]
4. Account Link SDK - Consume External Account Webhooks
NOTE: Your Platform must be configured with a valid webhook URL in order to receive webhooks. Please get in touch with a Zero Hash representative so that they can set this up for you.
After the Customer successfully links an account on the SDK front end, the Platform can consume the external account webhook events.
Pending Status
All external accounts pass through the pending
status, even for a brief second. Example webhook payload:
{
"account_nickname": "johns-usdc-eth-wallet",
"external_account_id": "c476a81f-a29f-4e22-88db-1f521d7cf004",
"external_account_status": "pending",
"participant_code": "CUST01",
"timestamp": 1729195673718
}
Approved Status
Given Zero Hash performs validations on the front end, in theory 100% of the created external accounts will go into an approved
status. Example webhook payload:
{
"account_nickname": "johns-usdc-eth-wallet",
"external_account_id": "c476a81f-a29f-4e22-88db-1f521d7cf004",
"external_account_status": "approved",
"participant_code": "CUST01",
"timestamp": 1729195673719
}
Closed Status
Platforms have the ability to close already-created external accounts via the POST /payments/external_accounts/{external_account_id}/close endpoint. This will also trigger a webhook event. Example payload:
{
"account_nickname": "johns-usdc-eth-wallet",
"external_account_id": "c476a81f-a29f-4e22-88db-1f521d7cf004",
"external_account_status": "closed",
"participant_code": "CUST01",
"timestamp": 1729195673720
}
Locked Status
Zero Hash's Compliance and Transaction Monitoring team are constantly reviewing external accounts and participants associated with those accounts to ensure that regulations are being followed, bad actors are unable to transact, etc. If we manually intervene and are looking to block activity related to an external account, the team will place the account initially into a locked
status while they perform further analysis on the account. Example webhook payload:
{
"account_nickname": "johns-usdc-eth-wallet",
"external_account_id": "c476a81f-a29f-4e22-88db-1f521d7cf004",
"external_account_status": "locked",
"participant_code": "CUST01",
"timestamp": 1729195673721
}
Disabled Status
Once Zero Hash's Compliance and Transaction Monitoring team have conducted its analysis, it's possible that the external account will be closed. The external account status will then reflect this. Example webhook payload:
{
"account_nickname": "johns-usdc-eth-wallet",
"external_account_id": "c476a81f-a29f-4e22-88db-1f521d7cf004",
"external_account_status": "closed",
"participant_code": "CUST01",
"timestamp": 1729195673722
}
5. Account Link SDK - Query External Accounts
The Platform can also query the GET /payments/external_accounts to view information about the linked account. Example response:
{
"request_id": "53fb4efd-bf98-4a48-8d89-11097983793e",
"message": [
{
"external_account_id": "c476a81f-a29f-4e22-88db-1f521d7cf004",
"account_nickname": "",
"participant_code": "CUST01",
"platform_code": "PLAT01",
"created_at": "2024-10-26T01:11:49.077Z",
"updated_at": "2024-10-26T01:11:49.149Z",
"status": "approved",
"status_reason": "",
"type": "crypto",
"details": {
"network": "ETH",
"supported_assets": [
"USDC"
],
"address": "0xa6b0Cd1baaa15AE97D8135f0E87F61af27c6cB89",
"destination_tag": ""
}
},
6. Crypto Withdraw SDK - Request Access Token
Now that the external account has been created, the Platform can use the Crypto Withdraw SDK. The next step is to request an access token specifying:
Field | Description | Example Value | Required? |
---|---|---|---|
participant_code | The 6-digit alpha numeric code associated with the Customer that is looking to withdraw (the response of the original POST /participants/customers/new call) | CUST01 | Y |
permissions | The array of permissions that will be granted to the returned JWT token | ["crypto-withdrawals"] | Y |
external_account_id | The UUID associated with the external account | 0f68333e-2114-469d-b505-c850d776e063 | Y |
quoted_asset | The fiat currency being used to fund the trade | USD | Y |
withdrawal_request_amount | The amount, in quoted_asset terms, to be withdrawn | 200 | Y |
Example POST /client_auth_token
request:
{
"participant_code": "CUST01",
"permissions": ["crypto-withdrawals"],
"withdrawal_details": {
"external_account_id": "c476a81f-a29f-4e22-88db-1f521d7cf004",
"quoted_asset": "USD",
"withdrawal_request_amount": "200"
}
}
Starting the Withdrawal flow
After a successful POST /client_auth_token
call, the next step is to start the Withdrawal SDK
flow, see the example below for doing it in a React
application. Keep in mind that if you're using a Native Mobile App (Swift, Flutter, etc) instead of using zh-web-sdk
you should follow the WebView
approach, described here
You can see the full integration guide for Withdrawals on the link below
https://docs.zerohash.com/reference/sdk-modules-crypto-withdrawals
import React from 'react';
import ZeroHashSDK, { AppIdentifier } from 'zh-web-sdk';
const App = () => {
const sdk = new ZeroHashSDK({
// For production usage, please change this URL to https://web-sdk.zerohash.com
zeroHashAppsURL: "https://web-sdk.cert.zerohash.com",
cryptoWithdrawalsJWT: "<JWT_TOKEN_HERE>"
});
sdk.openModal({
appIdentifier: AppIdentifier.CRYPTO_WITHDRAWALS // "crypto-withdrawals"
})
return <></>;
}
export default App;
The Customer will be shown the Withdrawal screen with a pre-populated withdrawal request, highlighting the following data points:
Data Point | Description | Example |
---|---|---|
Withdrawal Request Amount | The amount of quoted_asset they want to withdraw | 200 |
Destination Wallet | The wallet nickname and address of where the withdrawal is going to | John's USDC on Ethereum Wallet (**cB89) |
Withdrawal Fee | The transaction fee being assessed on the withdrawal | 1.50 |
Network Fee | The blockchain-assessed network fee on the withdrawal, in quoted_asset terms | 1.25 |
Withdrawal Receive Amount | The amount of the stablecoin or crypto asset that the user will be receiving | 197.25 |
7. Crypto Withdraw SDK - Initiate Withdrawal
At this point, the Customer is interacting with the front end SDK. The Customer will ultimately initiate the withdrawal.
[front end designs of the SDK will be placed here when ready]
8. Crypto Withdraw SDK - Consume Payments Webhook
After the Customer successfully initiates the Withdrawal via the SDK, the Platform should be prepared to consume the payments webhook events
Status Summary
The Withdrawal will initially enter a status of submitted
. From here, it's possible (yet rare) that Zero Hash has issues internally processing the transaction. If this happens, it will enter a terminal status of failed
. When the transaction has been successfully broadcasted on-chain, it will enter a status of posted
. A terminal status of settled
is reached when the Withdrawal has been confirmed on-chain and received by the Customer.
Submitted Status
The Withdrawal will initially and breifly enter a status of submitted
. Example payload:
"payment_id":"0f68333e-2114-469d-b505-c850d776e061",
"obo_participant":{
"participant_code":"CUST01",
"account_group":"PLAT01",
"account_label":"general"
},
"payment_details":{
"withdrawal_request_id":"",
"trade_id":"b752503c-1c42-4dfe-ad1d-7b39da5db59c",
"on_chain_transaction_id":"",
"network_fee_notional":"",
"network_fee_quantity":"",
"destination_address":"0xa6b0Cd1baaa15AE97D8135f0E87F61af27c6cB89"
},
"asset":"USDC",
"network":"ETH",
"payment_type":"withdrawal",
"external_account_id":"c476a81f-a29f-4e22-88db-1f521d7cf004",
"participant_code":"CUST01",
"quantity":"",
"status":"submitted",
"created_at":"2024-09-26T13:05:22.657Z",
"updated_at":"2024-09-26T13:05:22.657Z",
"total":"125.50"
}
Posted Status
The withdrawal will transition into a status of posted
, which means that the asset has been broadcasted on-chain. Note the presence of the on_chain_transaction_id
field, which represents the on-chain hash. Typically it's expected for this to be represented to the Customer on the Platform's "Transaction History" or equivalent page in order to allow the Customer to trace the transaction. Example payload:
{
"payment_id":"0f68333e-2114-469d-b505-c850d776e061",
"obo_participant":{
"participant_code":"CUST01",
"account_group":"PLAT01",
"account_label":"general"
},
"payment_details":{
"withdrawal_request_id":"14f8ebb8-7530-4aa4-bef9-9d73d56313f3",
"trade_id":"b752503c-1c42-4dfe-ad1d-7b39da5db59c",
"on_chain_transaction_id":"0x55dfac6137387a81e32fc353fca45eea3124cd42564a4112192323add8dee1da",
"network_fee_notional":"1.25",
"network_fee_quantity":".00032",
"destination_address":"0xa6b0Cd1baaa15AE97D8135f0E87F61af27c6cB89"
f
"asset":"USDC",
"network":"ETH",
"payment_type":"withdrawal",
"external_account_id":"c476a81f-a29f-4e22-88db-1f521d7cf004",
"participant_code":"CUST01",
"quantity":"",
"status":"posted",
"created_at":"2024-09-26T13:05:22.657Z",
"updated_at":"2024-09-26T13:05:22.657Z",
"total":"197.25"
}
Settled Status
When the withdrawal transitions to a settled
status, the transaction has been fully settled on-chain and the balance should be reflected on the Customer's destination exchange or wallet account. Example payload:
{
"payment_id":"0f68333e-2114-469d-b505-c850d776e061",
"obo_participant":{
"participant_code":"CUST01",
"account_group":"PLAT01",
"account_label":"general"
},
"payment_details":{
"withdrawal_request_id":"14f8ebb8-7530-4aa4-bef9-9d73d56313f3",
"trade_id":"b752503c-1c42-4dfe-ad1d-7b39da5db59c",
"on_chain_transaction_id":"0x55dfac6137387a81e32fc353fca45eea3124cd42564a4112192323add8dee1da",
"network_fee_notional":"1.25",
"network_fee_quantity":".00032",
"destination_address":"0xa6b0Cd1baaa15AE97D8135f0E87F61af27c6cB89"
},
"asset":"USDC",
"network":"ETH",
"payment_type":"withdrawal",
"external_account_id":"c476a81f-a29f-4e22-88db-1f521d7cf004",
"participant_code":"CUST01",
"quantity":"",
"status":"settled",
"created_at":"2024-09-26T13:05:22.657Z",
"updated_at":"2024-09-26T13:05:22.657Z",
"total":"197.25"
}
Failed Status
In rare instances, the withdrawal may transition to a failed
status. This could be due to either Zero Hash processing issues or issues with the blockchain itself. Example payload:
{
"payment_id":"0f68333e-2114-469d-b505-c850d776e061",
"obo_participant":{
"participant_code":"CUST01",
"account_group":"PLAT01",
"account_label":"general"
},
"payment_details":{
"withdrawal_request_id":"14f8ebb8-7530-4aa4-bef9-9d73d56313f3",
"trade_id":"b752503c-1c42-4dfe-ad1d-7b39da5db59c",
"on_chain_transaction_id":"0x55dfac6137387a81e32fc353fca45eea3124cd42564a4112192323add8dee1da",
"network_fee_notional":"1.25",
"network_fee_quantity":".00032",
"destination_address":"0xa6b0Cd1baaa15AE97D8135f0E87F61af27c6cB89"
},
"asset":"USDC",
"network":"ETH",
"payment_type":"withdrawal",
"external_account_id":"c476a81f-a29f-4e22-88db-1f521d7cf004",
"participant_code":"CUST01",
"quantity":"",
"status":"failed",
"created_at":"2024-09-26T13:05:22.657Z",
"updated_at":"2024-09-26T13:05:22.657Z",
"total":"197.25"
}
9. Crypto Withdraw SDK - Query Payments
The Platform can also query the GET /payments to view information about the withdrawal. Example response:
{
"request_id": "a502a26d-3734-497e-826b-d8d5734221e7",
"participant_code": "CUST01",
"platform_code": "PLAT01",
"obo_participant": {
"participant_code": "CUST01",
"account_group": "PLAT01",
"account_label": "general"
},
"payment_id": "0f68333e-2114-469d-b505-c850d776e061",
"asset": "USDC",
"network": "ETH",
"quoted_asset": "USD",
"status": "submitted",
"external_account_id": "c476a81f-a29f-4e22-88db-1f521d7cf004",
"created_at": "2024-11-15T23:02:06.836Z",
"total": "197.25"
}
10. Complete End of Day (EOD) Settlement
After each session, the Platform will be required to top up their float account to its original level. This is referred to as a Net Delivery Obligation (NDO). First, here are the standard settlement session and its schedules:
Session | NDO Wire Due |
---|---|
Monday | By Tuesday EOD |
Tuesday | By Wednesday EOD |
Wednesday | By Thursday EOD |
Thursday | By Friday EOD |
Friday | By Monday EOD |
For Bank Holidays in the US, the NDO will be due during the next valid business day.
Updated about 5 hours ago