Enterprise Settlements API Integration Guide
Seamlessly and compliantly disperse stablecoins to merchant customers through the zerohash API
API Solution
Companies that serve business customers (e.g., PSPs supporting merchants) can use the Enterprise Settlements API Solution to programmatically convert fiat to stablecoins and settle funds on-chain to merchant-owned wallets. This solution follows the same end-to-end settlement flow as the no-code experience, while giving teams full control through APIs and requiring engineering integration.
1. Submit Merchant (KYB)
Companies that use this product will be called "Platforms". Platforms will be responsible for onboarding their Merchant customers to zerohash programmatically through the API.
For the following examples, assume the Platform's platform code is PLAT01.
Merchant Information
The Platform submits the Merchant via POST /participants/entity/new
{
"platform_code": "PLAT01",
"entity_name": "Merchant A",
"legal_name": "Merchant A Inc.",
"contact_number": "15553765432",
"website": "www.merchant.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 Merchant participant code that uniquely identifies the entity indefinitely. We’ll use MERCH01 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.
Merchant Documents
Your Merchant 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 Merchant 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": "MERCH01"
}
2. Fund float account
The Float Account is used to fund instant conversions of USD to stablecoins, as zerohash does not extend credit.
The Platform will fund the Float Account by sending fiat to the proper bank account, tagging each wire with a memo equal to the Platform's participant_code. Including the wire memo means that we can auto-credit the correct account when we receive the wire/ACH.
Here are the account details that correspond to our API:
| Float Account | |
|---|---|
| Participant code | 00SCXM |
| Account group | [Platform's participant code] |
| Account label | general |
| Account type | Available |
| Asset | USD |
| Wire Memo | PLAT01 - FLOAT |
3. Whitelist Merchant destination addresses via API
Once a Merchant has been onboarded to zerohash, the platform can whitelist their wallet addresses for payouts using the zerohash API.
The Platform will use the Merchant's participant_code to link an external account via POST /payments/external_accounts:
{
"participant_code": "MERCH01",
"type": "crypto", // enum: crypto, fiat
"details": {
"network": "ETH",
"supported_assets": ["USDC"],
"address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
}
}
POST /payments/external_accounts response:
{
"request_id": "a9e2c6fb-f738-4ecb-986c-befd70678707",
"external_account_id": "107e8a2a-c835-4b76-b49d-a633d45727b9",
"participant_code": "MERCH01",
"platform_code": "PLAT01",
"account_nickname": "",
"created_at": "2024-10-11T00:52:21.865Z",
"status": "pending",
"type": "crypto",
"details": {
"network": "ETH",
"supported_assets": [
"USDC"
],
"address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"destination_tag": ""
}
}
Create External Account Rejection Scenarios
| Scenario | Response |
|---|---|
Merchant is not in an approved status | "participant is not approved" |
Merchant participant_code does not exist | "participant {participant_code} not found" |
Invalid supported_assets and/or network. The supported_asset.name/network combination results in a supported Payout asset. Official list of supported currency/networks:• USDC.ALGO • USDC.ARBITRUM • USDC.AVAX • USDC.AVAX • USDC.BASE • USDC.ETH • USDC.HBAR • USDC.OPT • USDC.POLYGON • USDC.SOL • USDC.SUI • USDC.XLM • USDC.ZKSYNC | "{supported_assets.network} is not supported" |
address is within the zerohash-maintained blacklist (ie, OFAC sanctioned address) | "invalid or denied destination address" |
Note: Each Merchant can have a maximum of 100 external accounts
Query Merchants' External Accounts
Platforms can view all previously-connected external accounts via GET /payments/external_accounts.
4. Initiate transactions
The Platform can initiate payout transactions via the POST /payments endpoint:
You can add an optional free-form description field, have left it out of the example.
{
"participant_code": "MERCH01",
"obo_participant": {
"participant_code": "PAYOR1",
"account_group": "PLAT01",
"account_label": "general"
},
"external_account_id": "2cc93b20-ee43-4877-8cdc-863e61829015",
"asset":"USDC", // note: do not specify the network (ie USDC.BASE). the network is implied by the external account
"quoted_asset": "USD",
"total": "125.50",
"payment_type": "payout",
"description": ""
}
Note:
- The
participant_codewill be the Merchant - zerohash will assume USD is being used to fund the trade to start
- zerohash will validate on the following:
- The
external_account_idmust have the specifiedassetattached to it - The platform will not have to pass a value for
networkon the POST /payments, it can be inferred from the account
- The
Response example:
{
"request_id": "0f68333e-2114-469d-b505-c850d776e063",
"participant_code": "MERCH01",
"obo_participant": {
"participant_code": "PAYOR1",
"account_group": "PLAT01",
"account_label": "general"
},
"payment_id": "0f68333e-2114-469d-b505-c850d776e061",
"asset": "USDC",
"network": "ETH",
"total": "125.50",
"quoted_asset": "USD",
"status": "submitted",
"external_account_id": "2cc93b20-ee43-4877-8cdc-863e61829015",
"created_at": "20243-08-19T23:15:05.000Z"
}
Note:
- The
payment_idfield represents the zerohash payment UUID, not the blockchain hash. - The payout will initially enter a status of
submitted. The address will be run through various address checks once again. If it passes, the payout will continue. Else, the status will becomefailedwith afailure_reasonof “address_failed_check”.
Initiate Payout Rejection Scenarios
| Scenario | Response |
|---|---|
external_account_id is not in an approved status | "account is not approved" |
external_account_id does not exist | "account not found" |
address is within the zerohash-maintained blacklist (ie, OFAC sanctioned address). in this scenario, the status of the external_account_id will remain in an approved state | "invalid or denied destination address" |
Merchant is not in an approved status | "participant is not approved" |
Merchant (participant_code) does not exist | "participant {participant_code} not found" |
Payor (obo_participant_code.participant_code) is not in an approved status | "participant is not approved" |
| Payor does not exist | "participant {participant_code} not found" |
total not valid• Must be a number • Numbers after decimal must be a maximum of the precision supported by the currency associated with the external_account_id (see here for precision details) | "missing valid value(s) for required field(s): Total" |
payment_type not valid• must be payout | "payment_type should be equal to one of the allowed values" |
| Float account balance is insufficient compared to the Payout amount | "insufficient balance" |
| Invalid quoted currency (USD is only supported at the moment) | "invalid quoted asset" |
The asset must be a supported_asset in the external account | "asset not supported" |
5. zerohash converts USD to stablecoins and sends stablecoins on-chain
Under the hood, zerohash will create a trade, which will take US dollars from the Payor USD Account, account details:
- Participant_code: PAYOR1
- Account_group: PLAT01
- Account_label: general
- Asset: USD
- Amount: 100,000
And convert them to crypto, resulting in a credit to the Payor USDC account, account details:
- Participant_code: PAYOR1
- Account_group: PLAT01
- Account_label: general
- Asset: USDC
- Amount: 125.50
zerohash will then initiate a withdrawal of the crypto from the Payor USDC account to the address associated with the external_account_id, associated with the payout (payment_id).
Immediately after the withdrawal, the payout will enter a status of posted - signifying that the USDC funds (ie, the withdrawal) has been settled on the zerohash ledger and the transaction has been dispersed on-chain.
Once the withdrawal has confirmed on-chain, the payout will transition to its final status of settled.
Payouts Webhook Examples
submitted example:
{
"payment_id":"0f68333e-2114-469d-b505-c850d776e061",
"obo_participant":{
"participant_code":"PAYOR1",
"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":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
},
"asset":"USDC",
"network":"ETH",
"payment_type":"payout",
"external_account_id":"2cc93b20-ee43-4877-8cdc-863e61829015",
"participant_code":"MERCH01",
"quantity":"",
"status":"submitted",
"created_at":"2024-09-26T13:05:22.657Z",
"updated_at":"2024-09-26T13:05:22.657Z",
"total":"125.50"
}
posted example:
{
"payment_id":"0f68333e-2114-469d-b505-c850d776e061",
"obo_participant":{
"participant_code":"PAYOR1",
"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":"FLaUcxdNxRwnaSXp6pXeRSfANXEHouqYbqF6X1bgRxg2",
"network_fee_notional":".01",
"network_fee_quantity":".001",
"destination_address":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
},
"asset":"USDC",
"network":"ETH",
"payment_type":"payout",
"external_account_id":"2cc93b20-ee43-4877-8cdc-863e61829015",
"participant_code":"MERCH01",
"quantity":"",
"status":"posted",
"created_at":"2024-09-26T13:05:22.657Z",
"updated_at":"2024-09-26T13:05:22.657Z",
"total":"125.50"
}
settled example:
{
"payment_id":"0f68333e-2114-469d-b505-c850d776e061",
"obo_participant":{
"participant_code":"PAYOR1",
"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":"FLaUcxdNxRwnaSXp6pXeRSfANXEHouqYbqF6X1bgRxg2",
"network_fee_notional":".01",
"network_fee_quantity":".001",
"destination_address":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
},
"asset":"USDC",
"network":"ETH",
"payment_type":"payout",
"external_account_id":"2cc93b20-ee43-4877-8cdc-863e61829015",
"participant_code":"MERCH01",
"quantity":"",
"status":"settled",
"created_at":"2024-09-26T13:05:22.657Z",
"updated_at":"2024-09-26T13:05:22.657Z",
"total":"125.50"
}
failed example:
{
"payment_id":"0f68333e-2114-469d-b505-c850d776e061",
"obo_participant":{
"participant_code":"PAYOR1",
"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":"FLaUcxdNxRwnaSXp6pXeRSfANXEHouqYbqF6X1bgRxg2",
"network_fee_notional":".01",
"network_fee_quantity":".001",
"destination_address":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
},
"asset":"USDC",
"network":"ETH",
"payment_type":"payout",
"external_account_id":"2cc93b20-ee43-4877-8cdc-863e61829015",
"participant_code":"MERCH01",
"quantity":"",
"status":"failed",
"created_at":"2024-09-26T13:05:22.657Z",
"updated_at":"2024-09-26T13:05:22.657Z",
"total":"125.50"
}
Network Fee Procedure
The Platform will be paying for the network fee associated with any payouts. There are 2 options:
- Fund the network fee account. Account details:
- Participant_code: PLAT01 (the Platform's participant_code)
- Account_group: PLAT01
- Account_label: network_fee
- Asset: USD
All network fees will paid out of this account in real-time
- Don’t fund the network fee account. This account will build up a payable balance
zerohash will add the amount of the receivable balance on this account to the end of month invoice, coupling it with the payout usage activity amount due.
Email Receipt Requirements
zerohash requires that the Payor receives an email receipt for each payout. We also insist that the email contain
- Certain fields and values, which can be obtained using our API
- Adequate support contact information
Required Fields:
| Email Receipt Field Name | Description | Example | API Location |
|---|---|---|---|
| Order Number | Unique order identifier | 9a738372-0855-4b25-8c65-5de0aa858b8b | payment_id from GET /payments or POST /payments response |
| Order Type | The type of order | Payout Transmission (must present this value verbatim) | N/a, can hard-code to "Payout Transmission" |
| Transmission Amount | The transmission amount for the payout, in quoted currency terms | $10 | total GET /payments or POST /payments response |
| Amount Received by Merchant | The amount of the underlying payout received by the Merchant | 10 USDC | total GET /payments or POST /payments response |
| Fees | Any 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/Time | Date and timestamp of the payout's transmission (meaning the time that the fiat was converted and settled into USDC within the zerohash system) | 2024-10-11 18:38:00 | created_at from GET /payments or POST /payments response |
| Account ID | zerohash’s unique account identifier for the Payor ( the “participant code” within zerohash). | BENEF1 | participant_code from GET /payments or POST /payments response |
Support Information:
| Field | Value (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 zerohash directly |
| zerohash Contact Information | Zero 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 |
6. Complete end of day settlement (T+1)
It is a regulatory requirement of zerohash's for the Platform to perform settlement on a T+1 basis. For example, if a Platform executed $1,000,000 worth of payouts on Monday, on Tuesday we expect a wire of $1,000,000 in order to consider Monday's settlement "closed".
7. View transaction activity
The Platform can view all payouts via GET /payments (shown initially in a submitted status below):
{
"message": [
{
"payment_id": "0f68333e-2114-469d-b505-c850d776e061",
"obo_participant": {
"participant_code": "PAYOR1",
"account_group": "PLAT01",
"account_label": "general"
},
"payment_details": {
"withdrawal_id": "",
"trade_id": "",
"on_chain_transaction_id ": "",
"network_fee_notional": "",
"network_fee_quantity": "",
}
"asset": "USDC",
"network": "ETH",
"payment_type":"payout",
"external_account_id": "2cc93b20-ee43-4877-8cdc-863e61829015"
"participant_code": "MERCH01",
"amount": "125.50",
"status": "submitted",
"failure_reason": "",
"created_at": "2024-08-19T23:15:30.000Z",
"updated_at": "2024-08-19T23:15:35.000Z"
}
]
}
Platforms can also query an individual payout via the GET /payments/id endpoint. Example:
{
"message":
{
"payment_id": "0f68333e-2114-469d-b505-c850d776e061",
"obo_participant": {
"participant_code": "PAYOR1",
"account_group": "PLAT01",
"account_label": "general"
},
"payment_details": {
"withdrawal_id": "",
"trade_id": "",
"on_chain_transaction_id ": "",
"network_fee_notional": "",
"network_fee_quantity": "",
}
"asset": "USDC",
"network": "ETH",
"payment_type":"payout",
"external_account_id": "2cc93b20-ee43-4877-8cdc-863e61829015"
"participant_code": "MERCH01",
"amount": "125.50",
"status": "submitted",
"failure_reason": "",
"created_at": "2024-08-19T23:15:30.000Z",
"updated_at": "2024-08-19T23:15:35.000Z"
}
}
When the withdrawal has settled on the zerohash ledger and directly after the on-chain transfer is sent on-chain, the Payout will transition to a posted status and will have non-null values for withdrawal_request_id, trade_id, on_chain_transaction_id, network_fee_notional, and network_fee_quantity:
{
"message": [
{
"payment_id": "0f68333e-2114-469d-b505-c850d776e061",
"obo_participant": {
"participant_code": "PAYOR1",
"account_group": "PLAT01",
"account_label": "general"
},
"payment_details": {
"withdrawal_request_id": "abc123",
"trade_id": "def456",
"on_chain_transaction_id ": "FLaUcxdNxRwnaSXp6pXeRSfANXEHouqYbqF6X1bgRxg2",
"network_fee_notional": ".01",
"network_fee_quantity": ".001",
"destination_address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
}
"asset": "USDC",
"network": "ETH",
"payment_type":"payout",
"external_account_id": "2cc93b20-ee43-4877-8cdc-863e61829015"
"participant_code": "MERCH01",
"amount": "125.50",
"status": "posted",
"failure_reason": "",
"created_at": "2024-08-19T23:15:30.000Z",
"updated_at": "2024-08-19T23:15:38.000Z"
}
]
}
When the withdrawal has been confirmed on-chain, the payout will transition to a settled status. Example payload:
{
"message": [
{
"payment_id": "0f68333e-2114-469d-b505-c850d776e061",
"obo_participant": {
"participant_code": "PAYOR1",
"account_group": "PLAT01",
"account_label": "general"
},
"payment_details": {
"withdrawal_request_id": "abc123",
"trade_id": "def456",
"on_chain_transaction_id ": "FLaUcxdNxRwnaSXp6pXeRSfANXEHouqYbqF6X1bgRxg2",
"network_fee_notional": ".01",
"network_fee_quantity": ".001",
"destination_address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
}
"asset": "USDC",
"network": "ETH",
"payment_type":"payout",
"external_account_id": "2cc93b20-ee43-4877-8cdc-863e61829015"
"participant_code": "MERCH01",
"amount": "125.50",
"status": "settled",
"failure_reason": "",
"created_at": "2024-08-19T23:15:30.000Z",
"updated_at": "2024-08-19T23:15:38.000Z"
}
]
}
Updated 1 day ago
