Payouts SDK Integration Guide

Always-on global payouts at internet speed - end to end technical integration guide

Introduction

The Payouts SDK product allows marketplaces, payment service providers, employer of record platforms, and more to offer stablecoins or crypto as a payout option.

Definitions

TermDefinition
PayoutThe conversion of fiat to stablecoin and an automatic on-chain transfer to a destination address.
PlatformThe company that's in contract with Zero Hash and directly interacts with Zero Hash's API's or SDK's.
PayorA non-natural person (typically a business) that is a customer of the Platform. This is the core front-end that the Beneficiary interacts with.
BeneficiaryThe natural person that receives the stablecoin payout.

High Level Flow

  1. Submit Payor
  2. Query Payor
  3. Fund Float Account
  4. Submit Beneficiary
  5. Query Beneficiary
  6. Connect External Account NEW (SDK)
  7. Query External Accounts
  8. Initiate Payout NEW (SDK)
  9. Query Payouts
  10. Network Fee Procedures
  11. Email Receipt Requirements

1. Submit Payor

Once API keys are created and approved, the Platform can begin to integrate to the API. For the following examples, we’ll assume the Platform's platform code is PLAT01.

Payor Information

The Platform submits the Payor via POST /participants/entity/new

{
    "platform_code": "PLAT01",
    "entity_name": "Freelancer Platform XYZ",
    "legal_name": "Freelancer Platform XYZ Inc.",
    "contact_number": "15553765432",
    "website": "www.freelancerplatform.com",
    "date_established": "2018-01-15",
    "entity_type": "llc",
    "address_one": "1 Main St.",
    "address_two": "Suite 1000",
    "city": "Chicago",
    "postal_code": "12345",
    "jurisdiction_code": "US-IL",
    "tax_id": "883987654",
    "id_issuing_authority": "United States",
    "risk_rating": "low",
    "risk_vendor": "passbase",
    "sanction_screening": "pass",
    "sanction_screening_timestamp":1677252628000,
    "metadata":{},
    "signed_timestamp":1677252629000,
    "submitter_email": "[email protected]",
    "submitter_first_name": "Josh"  // new addition to these specs 11.6.24 (to be a required field in the future)
    "submitter_last_name": "Doe"  // new addition to these specs 11.6.24 (to be a required field in the future)
    "submitter_title": "Senior Legal Council" // new addition to these specs 11.6.24 (to be a required field in the future)
    "control_persons":[
      {
        "name": "Joe Doe",
        "email": "[email protected]",
        "address_one": "1 South St.",
        "address_two": "Suite 2000",
        "city": "Chicago",
        "postal_code": "12345",
        "jurisdiction_code": "US-IL",
        "date_of_birth": "1980-01-30",  
        "citizenship_code": "US", 
        "tax_id": "123456789",
        "id_number_type": "us_passport",
        "id_number": "332211200",
        "kyc": "pass",
        "kyc_timestamp": 1630623005000,
        "sanction_screening":"pass",
        "sanction_screening_timestamp":1677252628000,
        "control_person": 1
      }
    ],
    "beneficial_owners":[
      {
        "name": "Jane Doe Jr",
        "beneficial_owner":1,
        "email": "[email protected]",
        "address_one": "1 North St.",
        "address_two": "Suite 3000",
        "city": "Chicago",
        "postal_code": "12345",
        "jurisdiction_code": "US-IL",
        "date_of_birth": "1980-01-30",
        "citizenship_code": "US", 
        "tax_id": "012345578",
        "id_number_type": "us_drivers_license",
        "id_number": "P11122243333",
        "kyc": "pass",
        "kyc_timestamp": 1630623005000,
        "sanction_screening": "pass",
        "sanction_screening_timestamp":1677252628000
      }
    ]
}

You’ll receive a participant_code in the response - this is the Payor participant code that uniquely identifies the entity indefinitely. We’ll use PAYOR1 as the participant_code throughout the examples.

See response for expected shape.

The Platform must submit at least 1 beneficial_owners and 1 control_persons. If these persons do not have an SSN, the Platform must submit a document via POST /participants/entity/documents.

Payor Documents

Your Payor will not become approved unless you also supply the proper documents via POST /participants/entity/documents endpoint. Depending on the entity_type that was used in the original POST /participants/entity/new call, the document requirements vary. See details here.

The Platform submits the Payor documents via POST /participants/entity/documents:

{
    "document": "...", // base 64 encoded file that you wish to upload (10mb limit)
    "mime": "image/png",
    "document_type": "articles_of_incorporation",
    "file_name": "test.png",
    "participant_code": "PAYOR1"
}

State Logic

After a successful POST /participants/entity/new submission, the initial status of the entity will be submitted. In order to transition this to approved, the Platform must then submit all required documents, via POST /participants/entity/documents.

2. Query Payor

Platforms can query already-submitted Payors via the GET /participants endpoint. If you'd like to query a specific Payor use theparticipant_code parameter.

3. Fund Float Account

The Platform will fund their float account by sending fiat to the proper bank account. Here are the account details:

  • Participant_code: 00SCXM
  • Account_group: PLAT01
  • Account_label: general
  • Account_type: available
  • Asset: USD

ℹ️

In Cert, your platform will be pre-funded with Float account funds.

4. Submit Beneficiary

The Platform submits a Beneficiary via POST /participants/beneficiaries/new

Available to select Platforms only: you can conditionally submit either a phone_number or an email- you must submit at least one, otherwise the request will fail.

{
    "first_name": "Lucas",
    "last_name": "Martinez",
    "email": "[email protected]",
    "address_one": "Calle San Martín 305",
    "address_two": "305",
    "city": "Buenos Aires",
    "zip": "C1000",
    "jurisdiction_code": "AR-X",
    "citizenship_code": "AR",
    "date_of_birth": "1985-09-02",
    "id_number_type": "non_us_passport",
    "id_number": "A12345678",
    "employment_status": "part_time", 
    "industry": "consulting",  
    "source_of_funds": "salary", 
    "signed_agreements": [
    {
        "type": "payment_services_terms",
        "region": "us",
        "signed_timestamp": 1726005278070
    }
]
}

NOTE:

  • Even in Cert, Zero Hash will run each submitted Beneficiary through our Sanction Checks, subjecting the Beneficiaries to real validations. This means that if your entry is an actual sanctioned individual, the Beneficiary will be sent to a rejected status. Another way to test the rejected scenario is to enter an SSN of "111111111". If you'd like to test an approved scenario - ensure you as close-to-reality sample as possible.
  • Zero Hash requires that the jurisdiction_code is not null and contains both a Country (ie, Brazil "BR") and a Subdivision (ie, Sao Palo "SP") to form BR-SP, for example. If your systems don't contain the subdivision and you only collect Country and Postal Code, we recommend performing a mapping on your side to derive the correct Subdivision, using the Country and Postal code as inputs. See this step-by-step guide on how to use an external API (ie, Google Geocoding API) to make a conversion between your data structure, to ours.

POST /participants/beneficiaries/new response:

{
    "message": {
        "first_name": "Lucas",
        "last_name": "Martinez",
        "email": "[email protected]",
        "address_one": "Calle San Martín 305",
        "address_two": "305",
        "jurisdiction_code": "AR-X",
        "city": "Buenos Aires",
        "zip": "C1000",
        "date_of_birth": "1985-09-02",
        "id_number_type": "non_us_passport",
        "id_number": "A12345678",
        "metadata": {},
        "platform_code": "PLAT01",
        "participant_code": "BENEF1",
        "citizenship_code": "AR",
        "phone_number": "",
        "signed_agreements": [
            {
                "region": "us",
                "signed_timestamp": 1726005278,
                "type": "payment_services_terms"
            }
        ],
        "employment_status": "part_time",
        "industry": "consulting",
        "source_of_funds": "salary"
    }
}

You’ll receive a participant_code in the response - this is the Beneficiary participant code that uniquely identifies the natural person indefinitely. We’ll use BENEF1 as the participant_code throughout the examples.

You should expect that Beneficiaries transition from submitted to approved, rejected, pending_approval ~instantly.

Beneficiary State Logic

After a successful POST /participants/beneficiaries/new submission, the initial status of the Beneficiary will be submitted. At this point, Zero Hash is running an automated compliance screening. If the person passes this check, the status will transition to an approved state. If the compliance screening results in a hit, the status will transition to a pending_approval status. Note: there is also a scenario where the Beneficiary transitions directly into a rejected state, depending on the compliance score. Zero Hash’s compliance team will become alerted and will manually review the Beneficiary within 24 hours. If the determination after that review is that the Beneficiary should not have been flagged, the status will transition to approved. Otherwise, the status will transition to rejected.

Beneficiary Rejection Scenario

In Cert, if the Platform would like to test a rejection scenario, please submit a Beneficiary like the following, passing an id_number equal to 111111111:

{
    "first_name": "Lucas",
    "last_name": "Martinez",
    "email": "[email protected]",
    "address_one": "Calle San Martín 305",
    "address_two": "305",
    "city": "Buenos Aires",
    "zip": "C1000",
    "jurisdiction_code": "AR-X",
    "citizenship_code": "AR",
    "date_of_birth": "1985-09-02",
    "id_number_type": "ssn",
    "id_number": "111111111",
    "employment_status": "part_time", 
    "industry": "consulting",  
    "source_of_funds": "salary", 
    "signed_agreements": [
    {
        "type": "payment_services_terms",
        "region": "us",
        "signed_timestamp": 1726005278070
    }
]
}

Beneficiary Pending Approval Scenario

In Cert, if the Platform would like to test a rejection scenario, please submit a Beneficiary like the following, passing an first_name equal to Joseph and last_name equal to Kony

{
    "first_name": "Joseph",
    "last_name": "Kony",
    "email": "[email protected]",
    "address_one": "Calle San Martín 305",
    "address_two": "305",
    "city": "Buenos Aires",
    "zip": "C1000",
    "jurisdiction_code": "AR-X",
    "citizenship_code": "AR",
    "date_of_birth": "1985-09-02",
    "id_number_type": "non_us_passport",
    "id_number": "A12345678",
    "employment_status": "part_time", 
    "industry": "consulting",  
    "source_of_funds": "salary", 
    "signed_agreements": [
    {
        "type": "payment_services_terms",
        "region": "us",
        "signed_timestamp": 1726005278070
    }
]
}

5. Query Beneficiary

Platforms can query already-submitted Beneficiaries via the GET /participants endpoint. A common query parameter is the participant_code, however the full list can be found on the API reference.

GET /participants/?participant_code=BENEF1 response:

{
    "message": [
        {
            "participant_code": "BENEF1",
            "participant_name": "Lucas Martinez",
            "email": "[email protected]",
            "status": "approved",
            "reason_code": null,
            "country": "Argentina",
            "state": "X",
            "jurisdiction_code": "AR-X",
            "updated_at": 1728607508503,
            "lifetime_remaining_limit": "0",
            "daily_remaining_limit": "0",
            "limit_currency": "USD",
            "limits": []
        }
    ],
    "page": 1,
    "page_size": 200,
    "total_pages": 1,
    "count": 1
}

6. Connect External Account

This is the first step where the Platform will invoke an SDK UI

6a. Account Link SDK - Request Access Token

The Platform should request an access token specifying:

FieldDescriptionExample ValueRequired?
participant_codeThe 6-digit alpha numeric code associated with the Customer that is looking to withdraw (the response of the original POST /participants/customers/new call)CUST01Y
permissionsThe 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",
    cryptoAccountLinkPayoutsJWT: "<JWT_TOKEN_HERE>" 
  });

    sdk.openModal({
    appIdentifier: AppIdentifier.CRYPTO_ACCOUNT_LINK_PAYOUTS, 
  })
  return <></>;
}

export default App;

6b. 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 with type CRYPTO_ACCOUNT_LINK_EXTERNAL_ACCOUNT_CREATED. The purpose of this event is to inform the Platform's Front end that the Customer's external_account was created and the Platform's FE can fetch that information from the GET /payments/external_accounts endpoint. Usually this will happen with the purpose of displaying these accounts on the screen for the Customer to choose from

Here is an example of how a React application could consume the CRYPTO_ACCOUNT_LINK_EXTERNAL_ACCOUNT_CREATED postMessage event:

 useEffect(() => {
    window.addEventListener('message', message => {
      if (message.data.type === 'CRYPTO_ACCOUNT_LINK_EXTERNAL_ACCOUNT_CREATED') {
        // A new external account for the end-user  was created, do stuff here
      }
    })
    return () => {
      window.removeEventListener('message', () => {})
    }
  }, [])

Once the account is successfully linked a Webhook Event will be sent, see more details on Section 4

6c. Account Link SDK - Consume External Account Webhooks

📘

NOTE: Your Platform must be configured with a valid webhook URL in order to receive these webhooks. Please get in touch with a Zero Hash representative so that they can set this up for you. Also, your Platform will need to be specifically enabled to receive these webhooks (the Zero Hash rep will also make this configuration).

After the Customer successfully links an account on the SDK front end, the Platform can consume the external account webhook events.

Note: the x-zh-hook-payload-type is external_account_status_changed (more details on webhooks here)

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
}

6d 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": ""
            }
        },

7. Initiate Payout

7a. Payouts 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:

FieldDescriptionExample ValueRequired?
participant_codeThe 6-digit alpha numeric code associated with the Customer that is looking to withdraw (the response of the original POST /participants/customers/new call)CUST01Y
permissionsThe array of permissions that will be granted to the returned JWT token["crypto-payouts"]Y
external_account_idThe UUID associated with the external account0f68333e-2114-469d-b505-c850d776e063Y
quoted_assetThe fiat currency being used to fund the tradeUSDY
withdrawal_request_amountThe amount, in quoted_asset terms, to be withdrawn200Y
reference_idPlatform reference id for a specific transaction0bd7f7f0-cf26-495f-b2df-e8afe8481ba3N

Example POST /client_auth_token request:

 {
    "participant_code": "CUST01",
    "permissions": ["crypto-payouts"],
    "withdrawal_details": {
        "external_account_id": "c476a81f-a29f-4e22-88db-1f521d7cf004",
        "quoted_asset": "USD",   
        "withdrawal_request_amount": "200"
    },
   "reference_id": "0bd7f7f0-cf26-495f-b2df-e8afe8481ba3"
}

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({
    zeroHashAppsURL: "https://web-sdk.cert.zerohash.com",
    payoutsJWT: "<JWT_TOKEN_HERE>" 
  });

    sdk.openModal({
    appIdentifier: AppIdentifier.CRYPTOPAYOUTS,
  })
  return <></>;
}

export default App;

The Customer will be shown the Withdrawal screen with a pre-populated withdrawal request, highlighting the following data points:

Data PointDescriptionExample
Transfer Request AmountThe amount of quoted_asset they want to transfer200
Destination WalletThe wallet nickname and address of where the Payout is going toJohn's USDC on Ethereum Wallet (**cB89)
Withdrawal FeeThe transaction fee being assessed on the withdrawal1.50
Network FeeThe blockchain-assessed network fee on the withdrawal, in quoted_asset terms1.25
Withdrawal Receive AmountThe amount of the stablecoin or crypto asset that the user will be receiving197.25

7b. Payouts SDK - Initiate Payout

At this point, the Customer is interacting with the front end SDK. The Customer will ultimately initiate the Payout.

7c. Payouts SDK - Consume Payments Webhook

📘

NOTE: Your Platform must be configured with a valid webhook URL in order to receive these webhooks. Please get in touch with a Zero Hash representative so that they can set this up for you. Also, your Platform will need to be specifically enabled to receive these webhooks (the Zero Hash rep will also make this configuration).

After the Customer successfully initiates the payout via the SDK, the Platform should be prepared to consume the payments webhook events

Note: the x-zh-hook-payload-type is payment_status_changed (more details on webhooks here)

Status Summary

The Payout 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 briefly 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":"",
      "withdrawal_fee_notional": "",
      "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":"200",
   "reference_id": "0bd7f7f0-cf26-495f-b2df-e8afe8481ba3"
}

Posted Status

The Payout 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",
      "withdrawal_fee_notional": "1.50",
      "destination_address":"0xa6b0Cd1baaa15AE97D8135f0E87F61af27c6cB89"
   f
   "asset":"USDC",
   "network":"ETH",
   "payment_type":"withdrawal",
   "external_account_id":"c476a81f-a29f-4e22-88db-1f521d7cf004",
   "participant_code":"CUST01",
   "quantity":"197.25",
   "status":"posted",
   "created_at":"2024-09-26T13:05:22.657Z",
   "updated_at":"2024-09-26T13:05:22.657Z",
   "total":"200",
   "reference_id": "0bd7f7f0-cf26-495f-b2df-e8afe8481ba3"
}  

Settled Status

When the Payout 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",
      "withdrawal_fee_notional": "1.50",
      "destination_address":"0xa6b0Cd1baaa15AE97D8135f0E87F61af27c6cB89"
   },
   "asset":"USDC",
   "network":"ETH",
   "payment_type":"withdrawal",
   "external_account_id":"c476a81f-a29f-4e22-88db-1f521d7cf004",
   "participant_code":"CUST01",
   "quantity":"197.25",
   "status":"settled",
   "created_at":"2024-09-26T13:05:22.657Z",
   "updated_at":"2024-09-26T13:05:22.657Z",
   "total":"200",
   "reference_id": "0bd7f7f0-cf26-495f-b2df-e8afe8481ba3"
}  

Failed Status

In rare instances, the Payout 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",
      "withdrawal_fee_notional": "1.50",
      "destination_address":"0xa6b0Cd1baaa15AE97D8135f0E87F61af27c6cB89"
   },
   "asset":"USDC",
   "network":"ETH",
   "payment_type":"withdrawal",
   "external_account_id":"c476a81f-a29f-4e22-88db-1f521d7cf004",
   "participant_code":"CUST01",
   "quantity":"197.25",
   "status":"failed",
   "created_at":"2024-09-26T13:05:22.657Z",
   "updated_at":"2024-09-26T13:05:22.657Z",
   "total":"200",
   "reference_id": "0bd7f7f0-cf26-495f-b2df-e8afe8481ba3"
}  

7d. Payouts SDK - Query Payments

The Platform can also query the GET /payments to view information about the Payout. 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": "200",
    "reference_id": "0bd7f7f0-cf26-495f-b2df-e8afe8481ba3"
}

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:

SessionNDO Wire Due
MondayBy Tuesday EOD
TuesdayBy Wednesday EOD
WednesdayBy Thursday EOD
ThursdayBy Friday EOD
FridayBy Monday EOD

For Bank Holidays in the US, the NDO will be due during the next valid business day.

11. Email Receipt Requirements

Zero Hash requires that the Payor receives an email receipt for each Payout. We also insist that the email contain

  1. Certain fields and values, which can be obtained using our API
  2. Adequate support contact information

Fields

Here are the required field names and their associated API fields:

Email Receipt Field NameDescriptionExampleAPI Location
Order NumberUnique order identifier9a738372-0855-4b25-8c65-5de0aa858b8bpayment_id from GET /payments or POST /payments response
Order TypeThe type of orderPayout Transmission

(must present this value verbatim)
N/a, can hard-code to "Payout Transmission"
Transmission AmountThe transmission amount for the Payout, in quoted currency terms$10total GET /payments or POST /payments response
Amount Received by BeneficiaryThe amount of the underlying Payout received by the Beneficiary10 USDCtotal GET /payments or POST /payments response
FeesAny added fee values included in the Payout. If there are no fees, then this needs to be expressly stated.

This includes processing fees (ie, a fee assessed by the Platform to the Payor) or blockchain network fees.
"$0", "1.99", "$0 Fees", or "No Fees"- Payouts does not currently support processing fees

- If the Payor is incurring the network fee, take network_fee_notional from [GET /payments]. If not, no need to include it in the receipt
Date/TimeDate and timestamp of the Payout's transmission (meaning the time that the fiat was converted and settled into USDC within the Zero Hash system)2024-10-11 18:38:00created_at from GET /payments or POST /payments response
Account IDZero Hash’s unique account identifier for the Payor ( the “participant code” within Zero Hash).BENEF1participant_code from GET /payments or POST /payments response

Support Information

FieldValue (examples)
Platform Contact Email [email protected]
Platform Phone(123) 123-1234
Platform Address 123 Main Street
New York, NY 10014
Platform Support Contact Email (if different from above)[email protected] - this is an email that is used to contact Zero Hash directly
Zero Hash Contact InformationZero Hash LLC (NMLS ID 1699379)
327 N Aberdeen St
Chicago, IL 60607
855-744-7333
[email protected]
www.zerohash.com

This value should be included in the email verbatim