Withdrawals - Reconciliation
Platforms who manage a ledger on their end must abide by our conformance requirements
Context
Within the larger Account Funding product, Zero Hash offers the ability for Platforms to allow their end customers to make crypto and stablecoin withdrawals. The integration guide for the withdrawal flow is here. This page is targeted, providing Zero Hash's official requirements for Platforms who maintain balances on their side.
Most Platforms will manage the fiat ledger on their end. For example, consider a brokerage platform. On the deposit, Zero Hash will initially credit the user with USDC. We'll then convert to USD in the customer account and transfer those funds to the brokerage's account. At this point, Zero Hash relinquishes control of the end customer's account balance (ie, the ledger) to the brokerage. So when withdrawals are made, Zero Hash does not have the ability to perform an informed credit check.
The Platform is completely responsible for ensuring that the end customer does not withdraw more than their current balance. Do not release funds back to your customer unless you receive an explicit "failed" webhook.
Let's now look at the end-to-end flow, where certain Requirements are stated.
Requirements
- When the withdrawal SDK is invoked, the Platform must encumber (in other words, "lock") the funds on their system, preventing the end customer from using those funds until the withdrawal is in a terminal state of failed.
- If there is ever a case where a webhook cannot be matched (or otherwise "processed successfully") by the Platform, the funds that are encumbered must continue to be encumbered.
- The Platform shall only re-credit the end customer's balance if the withdrawal reaches a failed terminal state (
status
= failed or abandoned) - The Platform shall only re-credit the end customer's balance after querying the GET /payments/{payment_id} and observing
status
= failed or abandoned.- The GET /payments/{payment_id} endpoint will provide the current status of the withdrawal. Zero Hash maintains 24/7/365 support should there ever be any concerns about the status of a withdrawal. However, the utilization of
payment_id
allows this process to be self service and therefore affords a better end user experience.
- The GET /payments/{payment_id} endpoint will provide the current status of the withdrawal. Zero Hash maintains 24/7/365 support should there ever be any concerns about the status of a withdrawal. However, the utilization of
- The Platform must keep track of withdrawal states on their end. In order to progress the transaction through the Platform’s state machine, the webhooks must be matched per the instructions in the "Field matching logic" section. When building your state machine, you must persist Zero Hash's unique ID, the payment_id as well as any of your own (ie, the
reference_id
). You can usepayment_id
to self-serve the record via the GET /payments/{payment_id} REST endpoint. - Zero Hash offers the ability to attach a Platform-dictated
reference_id
on thePOST /client_auth_token
request. This ID will be sent on each associated webhook call. Thereference_id
is solely “metadata” attached to a withdrawal. You must only depend on thepayment_id
returned in the response of thePOST /client_auth_token
call to match webhooks.
We strongly encourage that the Platform set up alert automation to make your systems and people aware of a withdrawal that was unsuccessfully processed. The next step is to reach out to Zero Hash support - our operations team will assist in reconciling the state.
We strongly encourage that the Platform only allow 1 withdrawal at a time per
participant_code
. If you choose to allow more than one, the Platform should usecreated_at
to disambiguate multiple open withdrawal requests for the sameparticipant_code
. For example, the timestamp that the Platform invoked the SDK originally should be "close" the thecreated_at
value.
Withdrawal flow
You can view the official integration guide for and end to end overview
- Platform invokes Withdraw SDK
- End Customer initiates withdraw
- Zero Hash sends webhooks to Platform
1. Platform invokes Withdraw SDK
As a refresher, when invoking the Withdraw SDK, the Platform must specify the following fields:
external_account_id
(optional)quoted_asset
amount
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"
},
"reference_id": "0bd7f7f0-cf26-495f-b2df-e8afe8481ba3"
}
Example POST /client_auth_token
response:
{
"message": {
"token": "<JWT_TOKEN_RESPONSE>",
"payment_id": "0jld7f7f0-cf26-495f-b2df-e8afe8481re6"
}
}
There are 3 possible terminal states:
settled
- the withdrawal completed and the Platform can assume that the End Customer has its funds. The Platform needs to settle the outstanding amount with Zero Hash on the settlement cadence originally agreed upon.failed
- the Platform may re-credit the end customer's balance with theamount
abandoned
- the Platform may re-credit the end customer's balance with theamount
2. End Customer initiates withdraw
The End Customer will click the Initiate withdrawal
button on the Confirm Withdrawal details screen of the Withdraw SDK, which will trigger the submitted
webhook message.
3. Zero Hash sends webhooks to the Platform
Zero Hash will send a series of webhooks that correspond to the status of the withdrawal:
initialized
- The Platform has successfully generated a JWT via thePOST /client_auth_token
endpointsubmitted
- Zero Hash has received the withdrawal requestpending
- Zero Hash is processing the requestposted
- The withdrawal has been sent on-chainsettled
- The withdrawal has completed on-chain (the End Customer has their funds)abandoned
- The JWT token has expired before the withdrawal was initiated by the End Customer. Currently, each JWT token is valid for 5 minutes.failed
- The withdrawal was not successfully sent on-chain (the End Customer does not have their funds)
Field matching logic
To consider a withdrawal as "matched", the Platform's system must be able to perform a one-to-one match between the following field values included in the webhook message and the corresponding records on the Platform's side:
participant_code
,withdrawal_request_amount
andpayment_id
Scenarios
Zero Hash will administer an official conformance test, ensuring the following scenarios pass, before the Platform can go live.
Scenario Assumptions
- We'll assume the following
POST /client_auth_token
call
{
"participant_code": "CUST01",
"permissions": ["crypto-withdrawals"],
"withdrawal_details": {
"external_account_id": "c476a81f-a29f-4e22-88db-1f521d7cf004",
"quoted_asset": "USD",
"withdrawal_request_amount": "200"
},
"reference_id": "0bd7f7f0-cf26-495f-b2df-e8afe8481ba3"
}
- We'll assume the following
POST /client_auth_token
response contains apayment_id
=0po7f7f0-cf26-495f-b2df-e8afe8481yu2
1. Successful withdrawal
- End customer clicks the
Initiate withdrawal
on the Confirm Withdrawal details screen - Zero Hash sends the following webhooks:
initiatlized
- → The Platform updates their state machine for this transaction to
initialized
- → The Platform updates their state machine for this transaction to
submitted
- → The Platform updates their state machine for this transaction to
submitted
- → The Platform updates their state machine for this transaction to
pending
- → The Platform updates their state machine for this transaction to
pending
- → The Platform updates their state machine for this transaction to
posted
- → The Platform updates their state machine for this transaction to
posted
- → The Platform updates their state machine for this transaction to
settled
- → The Platform updates their state machine for this transaction to
settled
and importantly, ensures that the end customer balances remains deducted by the withdrawal amount.
- → The Platform updates their state machine for this transaction to
2. Failure Case: The Platform does not receive any webhooks
- End customer clicks the
Initiate withdrawal
on the Confirm Withdrawal details screen - Zero Hash sends the following webhooks:
initiatlized
- → The Platform updates their state machine for this transaction to
initialized
- → The Platform updates their state machine for this transaction to
submitted
- The Platform does not receive a
submitted
webhook - → The Platform does not update their state machine for this transaction to
submitted
- → At this point, the Platform realizes there is an issue - the
submitted
status was not recognized by the Platform. An alert that the Platform set up is triggered.. The next step that we recommend is to take thepayment_id
(Zero Hash's unique id for this transaction) and use that in the GET /payments/{payment_id} API call to attempt to get back to reconciliation. Given the matching logic stands true, you can use this endpoint to see the most recent status of the withdrawal.
- The Platform does not receive a
- Once back in a correct state by calling GET /payments/{payment_id} and seeing status =
pending
- → The Platform updates their state machine for this transaction to
pending
- → The Platform updates their state machine for this transaction to
- Once back in a correct state by calling GET /payments/{payment_id} and seeing status =
posted
- → The Platform updates their state machine for this transaction to
posted
- → The Platform updates their state machine for this transaction to
- Once back in a correct state by calling GET /payments/{payment_id} and seeing status =
settled
- → The Platform updates their state machine for this transaction to
settled
and importantly, ensures that the end customer balances remains deducted by the withdrawal amount.
- → The Platform updates their state machine for this transaction to
3. Zero Hash sends back a null payment_id in the webhook
- End customer clicks the
Initiate withdrawal
on the Confirm Withdrawal details screen - Zero Hash sends the following webhooks:
initiatlized
- → The Platform updates their state machine for this transaction to
initialized
- → The Platform updates their state machine for this transaction to
- The Platform receives a webhook with status =
submitted
butpayment_id
= null- → At this point, the Platform realizes there is an issue - the
payment_id
is null. An alert that the Platform set up is triggered. - The end customer’s balance must remain debited on the platform’s side (do not release the funds back to the end customer)
- → At this point, the Platform realizes there is an issue - the
- Assuming that for the rest of the webhooks (
pending
,posted
, andsettled
) the Platform receives messages without a non-nullpayment_id
, the expectation is the the Platform never returns the funds back to the end customer. You should reach out to Zero Hash escalating the issue
4. Zero Hash sends duplicate webhooks for a withdrawal that ends up succeeding on-chain
- End customer clicks the
Initiate withdrawal
on the Confirm Withdrawal details screen - Zero Hash sends the following webhooks:
initiatlized
- → The Platform updates their state machine for this transaction to
initialized
- → The Platform updates their state machine for this transaction to
submitted
- → The Platform updates their state machine for this transaction to
submitted
- → The Platform updates their state machine for this transaction to
pending
- → The Platform updates their state machine for this transaction to
pending
- → The Platform updates their state machine for this transaction to
posted
- → The Platform updates their state machine for this transaction to
posted
- → The Platform updates their state machine for this transaction to
posted
(2x)- → The Platform keeps their state machine for this transaction at
posted
- → The Platform keeps their state machine for this transaction at
settled
- → The Platform updates their state machine for this transaction to
settled
and importantly, ensures that the end customer balances remains deducted by the withdrawal amount.
- → The Platform updates their state machine for this transaction to
5. Zero Hash sends back a different payment_id than the one that was sent back in the POST /client_auth_token response
- End customer clicks the
Initiate withdrawal
on the Confirm Withdrawal details screen - Zero Hash sends the following webhooks:
initiatlized
- → The Platform updates their state machine for this transaction to
initialized
- → The Platform updates their state machine for this transaction to
- The Platform receives a webhook for
submitted
but thepayment_id
= 0647f7f0-cf26-495f-b2df-e8afe8481ty2 (different than what was consumed on thePOST /client_auth_token
response)- → The Platform does not update their state machine to
submitted
and keeps funds encumbered
- → The Platform does not update their state machine to
6. The withdrawal truly fails (for example, the blockchain has a processing issue) and the Platform should return the funds back to the End Customer
- End customer clicks the
Initiate withdrawal
on the Confirm Withdrawal details screen - Zero Hash sends the following webhooks:
initiatlized
- → The Platform updates their state machine for this transaction to
initialized
- → The Platform updates their state machine for this transaction to
submitted
- → The Platform updates their state machine for this transaction to
submitted
- → The Platform updates their state machine for this transaction to
pending
- → The Platform updates their state machine for this transaction to
pending
- → The Platform updates their state machine for this transaction to
posted
- → The Platform updates their state machine for this transaction to
posted
- → The Platform updates their state machine for this transaction to
rejected
- → The Platform updates their state machine for this transaction to
rejected
- the Platform can release the funds back to the End Customer
- → The Platform updates their state machine for this transaction to
7. The End Customer starts the withdrawal process, but doesn't end up initiating a withdrawal. The JWT token expires.
- Zero Hash sends the following webhooks:
initiatlized
- → The Platform updates their state machine for this transaction to
initialized
- → The Platform updates their state machine for this transaction to
- 5 minutes since the JWT token generation elapses. Zero Hash sends an
abandoned
webhook- → The Platform updates their state machine for this transaction to
abandoned
- the Platform can release the funds back to the End Customer
- → The Platform updates their state machine for this transaction to
Updated 1 day ago