Fund Integration Guide (Onboarding API + Fund API)
See core product page here
Setup
In this guide, the Platform is setup to use the Onboarding API (Reliance) plus the Fund API.
Flow
In this example, the Platform is configured to have the converted USD be automatically transferred to the Platform's account. We'll also assume that this is an investment platform that allows "issuers" to source investments from End Customers. Summary of all involved participants:
- Platform (participant_code: PLAT01)
- End Customer (participant_code: CUST01)
- Issuer 1 (participant_code: ISSUR1)
Webhook configuration
Talk to your Zero Hash representative to have your Platform configured for the following webhooks:
- Onboarding
- Fund
Submit customer
Example request - POST /participants/customers/new
{
"first_name": "John",
"last_name": "Smith",
"email": "[email protected]",
"phone_number": "9545551234",
"address_one": "1 Main St.",
"address_two": "Suite 1000",
"city": "Chicago",
"state": "IL",
"zip": "12345",
"country": "United States",
"date_of_birth": "1985-09-02",
"citizenship": "United States",
"tax_id": "123456789",
"risk_rating": "low",
"kyc": "pass",
"kyc_timestamp": 1630623005000,
"sanction_screening": "pass",
"sanction_screening_timestamp": 1630623005000,
"idv": "pass",
"liveness_check": "pass",
"signed_timestamp": 1630623005000,
"metadata": {},
"signed_agreements": [
{
"type": "fund_auto_convert",
"region": "us",
"signed_timestamp": 1712008721000
}
]
}
If you fail to indicate that the end customer has agreed to the fund-specific terms, in the signed_agreements
object shown above, /fund
API calls will fail.
There may be situations where the Platform is restricted from submitting Customers who reside in certain jurisdictions (ie, New York). The Platform will receive an error that looks like:
{
"errors": [
"The submitting platform is not allowed to operate in the participant's resident state",
"participant is not in an allowed jurisdiction"
]
}
The preferred approach is for the Platform to not allow Customers to onboard on their side (through a feature flag, for example). However, if a request with a Customer in a blocked jurisdiction does get submitted to Zero Hash via API, the Platform should fail gracefully and display a descriptive error message on-screen.
Get quote - POST /fund/rfq
Example request
{
"participant_code": "CUST01",
"fund_asset": "USDC.ETH",
"account_label": "ISSUR1"
}
Interpretation of this request - the end customer (CUST01) is funding their account using USDC as the asset and ETH as the blockchain network. And to be specific, the end customer is funding the account for the purposes of investing into the issuer 1's company. The platform uses the optional, free-form account_label field for reconciliation and reporting. The entirety of this fund event and related ledger movements will be tied to issuer 1.
Example response - POST /fund/rfq
{
"message": {
"request_id": "14f8ebb8-7530-4aa4-bef9-9d73d56313f3",
"participant_code": "CUST01",
"fund_asset": "USDC.ETH",
"rate": "1",
"quoted_currency": "USD",
"expiry_timestamp": 2554408627334,
"deposit_address": "0x5f59B625036ccB4f7aD27Ca4Cb896e4452AfFDAF",
"minimum_deposit": "1",
"maximum_deposit": "250000"
}
}
End customer initiates on-chain deposit
The end customer is presented with an address on the platform's user interface. They use their Metamask wallet, for example, to trigger a withdrawal into the deposit_address for the amount of their choice. Let's say the deposit is for 1,000 USDC.
Deposit received
The transaction has received the proper amount of on-chain confirmations (for USDC in this case, it is 1) and Zero Hash initiates a series of movements:
Accounts for this example:
account_id | account_owner | account_group | account_label | account_type | asset |
---|---|---|---|---|---|
47a854fa-d0e4-405b-831b-f1a86bbf7988 | CUST01 | PLAT01 | ISSUR1 | available | USDC.ETH |
52c29f2d-8298-4e8c-84bc-7773960bee12 | CUST01 | PLAT01 | ISSUR1 | available | USD |
- Deposit: Using our account nomenclature, the movement would appear as:
account_owner | account_group | account_label | account_type | movement_type | asset | change |
---|---|---|---|---|---|---|
CUST01 | PLAT01 | ISSUR1 | available | deposit | USDC.ETH | +1,000 |
- Conversion: Automatically, Zero Hash will convert the USDC to USD. This technically creates a trade, which has a movement_type of final_settlement:
account_owner | account_group | account_label | account_type | movement_type | asset | change |
---|---|---|---|---|---|---|
CUST01 | PLAT01 | ISSUR1 | available | final_settlement | USDC.ETH | -1,000 |
CUST01 | PLAT01 | ISSUR1 | available | final_settlement | USD | +1,000 |
- Transfer: Automatically, Zero Hash will transfer the USD from the end customer account to the platform's:
account_owner | account_group | account_label | account_type | movement_type | asset | change |
---|---|---|---|---|---|---|
CUST01 | PLAT01 | ISSUR1 | available | transfer | USD | -1,000 |
PLAT01 | PLAT01 | ISSUR1 | available | transfer | USD | +1,000 |
Webhook event triggered
The platform can subscribe to a newly built webhook event for this product. It will be triggered at the completion of step 3 above (or step 2 if you are not configured for a subsequent transfer). For more information on webhook messages for Fund, check out webhook documentation.
Platform queries REST fund endpoint
The platform can also retroactively query the GET /fund/transactions
endpoint to view details of all particular fund event. View the code recipe for additional assistance.
Example response - GET /fund/transactions
{
"message": [
{
"participant_code": "CUST01",
"fund_asset": "USDC.ETH",
"rate": "1",
"quoted_currency": "USD",
"source_address": "0x3A45a60c62EE6cD616B1C4510404Eba88116044I",
"deposit_address": "0x3A45a60c635E6cD616B1C4510404Eba88116050C",
"quantity": "1000",
"notional": "1000",
"success": true,
"fund_id": "5155f7c9-95cb-4556-ab89-c178943a7111",
"is_first_deposit": true,
"fund_timestamp": 1727380576846,
"deposit_timestamp": 1727380579415,
"transaction_id": "0x72e399164e29bada83075cd44eacfc40c1656bc35bf38f0cf661bf00dfa3b2b2",
"account_label": "ISSUR1",
"raw_fee_bps": "50",
"actual_fee_bps": "1000",
"raw_fee_notional": "0.05",
"actual_fee_notional": "1",
"deposited_asset": "USDC"
}
],
"page": 1,
"page_size": 50,
"total_pages": 1
}
Platform settlement - platform initiates a withdrawal request
The platform can then withdraw the USD using the POST /withdrawals/requests endpoint.
Example Request - POST /withdrawals/requests
{
"withdrawal_account_id":"abc12",
"participant_code":"PLAT01",
"amount":"1000",
"asset":"USD",
"account_group":"PLAT01"
}
Reconciliation - platform queries REST movements endpoint
The platform can now query GET /movements?parent_link_id=5155f7c9-95cb-4556-ab89-c178943a7111
in order to view all related ledger movements to this fund event.
Example response - GET /movements?parent_link_id=
{
"message": [
{
"movement_timestamp": 1550174570000,
"account_id": "47a854fa-d0e4-405b-831b-f1a86bbf7988",
"movement_id": "5d4d962d-dc37-4ddc-b0c5-b8980d1d4421",
"movement_type": "deposit",
"transfer_type": null,
"deposit_reference_id": null,
"withdrawal_request_id": null,
"parent_link_id": "5155f7c9-95cb-4556-ab89-c178943a7111",
"trade_id": null,
"change": "1000"
},
{
"movement_timestamp": 1550174571000,
"account_id": "47a854fa-d0e4-405b-831b-f1a86bbf7988",
"movement_id": "3d4d962d-dc37-4ddc-b0c5-b8980d1d4422",
"movement_type": "final_settlement",
"transfer_type": null,
"deposit_reference_id": null,
"withdrawal_request_id": null,
"parent_link_id": "5155f7c9-95cb-4556-ab89-c178943a7111",
"trade_id": null,
"change": "-1000"
},
{
"movement_timestamp": 1550174572000,
"account_id": "52c29f2d-8298-4e8c-84bc-7773960bee12",
"movement_id": "80818b7b-92ec-4882-a371-a59fb3f8bce0",
"movement_type": "final_settlement",
"transfer_type": null,
"deposit_reference_id": null,
"withdrawal_request_id": null,
"parent_link_id": "5155f7c9-95cb-4556-ab89-c178943a7111",
"trade_id": null,
"change": "1000"
},
{
"movement_timestamp": 1550174573000,
"account_id": "52c29f2d-8298-4e8c-84bc-7773960bee12",
"movement_id": "80818b7b-92ec-4882-a371-a59fb3f8bce0",
"movement_type": "transfer",
"transfer_type": null,
"deposit_reference_id": null,
"withdrawal_request_id": null,
"parent_link_id": "5155f7c9-95cb-4556-ab89-c178943a7111",
"trade_id": null,
"change": "-1000"
},
{
"movement_timestamp": 1550174574000,
"account_id": "77c29f2d-8298-4e8c-84bc-7773960bee10",
"movement_id": "80818b7b-92ec-4882-a371-a59fb3f8bce0",
"movement_type": "transfer",
"transfer_type": null,
"deposit_reference_id": null,
"withdrawal_request_id": null,
"parent_link_id": "5155f7c9-95cb-4556-ab89-c178943a7111",
"trade_id": null,
"change": "1000"
}
]
}
Platform settlement
Zero Hash will, one time per day, send a fiat settlement wire to the Platform where the amount represents the sum of all converted deposits from the prior trading session. Here is the settlement schedule:
Session | Start | End | Expected Settlement Time* |
---|---|---|---|
Monday | Monday 9:00a EST | Tuesday 8:59:59a EST | Tuesday EOD |
Tuesday | Tuesday 9:00a EST | Wednesday 8:59:59a EST | Wednesday EOD |
Wednesday | Wednesday 9:00a EST | Thursday 8:59:59a EST | Thursday EOD |
Thursday | Thursday 9:00a EST | Friday 8:59:59a EST | Friday EOD |
Friday | Friday 9:00a EST | Monday 8:59:59a EST | Monday EOD |
During US holidays, Platforms should expect their settlements to arrive by EOD on the next business day. For example, for the August 30th 2024 session, the settlement will arrive by Tuesday EOD (because Monday was Labor Day)
Tracking the settlement amount
The recommended way to track the impending settlement amount is to query the GET /accounts/{account_id} endpoint. In order to retrieve the correct account_id
to use in that call, use the following query parameters when hitting the GET /accounts endpoint:
participant_code
: [your platform code]account_group
: [your platform code]account_label
: generalaccount_type
: availableasset
: [fiat currency code, typically "USD"]
Use the account_id
in the response, ie af063ca6-836f-4677-8ebf-edbe1d049938
, to query GET /accounts/af063ca6-836f-4677-8ebf-edbe1d049938. Example response:
{
"message": [
{
"asset": "USD"
"account_owner": "PLAT01",
"account_type": "available",
"account_group": "PLAT01",
"account_label": "general",
"balance": "25000",
"account_id": "af063ca6-836f-4677-8ebf-edbe1d049938",
"last_update": 1727214271011
}
]
}
Retroactively querying settlements in the past
Platform settlements are technically a withdrawal
. In order to query settlements from the past, use the GET /withdrawals/requests endpoint. The response will show all withdrawals made from the Platform's account, or one of your Customer's (Customer withdrawals are extremely rare and reserved for edge case Fund failures only).
In order to filter for withdrawals made from the Platform account only, query GET /withdrawals/requests using the participant_code
query parameter, where the value is the Platform's platform code:
Example GET /withdrawals/requests?participant_code=PLAT01
response:
{
"message": [
{
"id": "152b8276-0585-45ec-bf62-85e134b3ff43",
"withdrawal_account_id": 346030,
"participant_code": "PLAT01",
"account_group": "PLAT01",
"account_label": null,
"requestor_participant_code": "PLAT01",
"asset": "USDC.SOL",
"requested_amount": "25000",
"settled_amount": "25000",
"gas_price": null,
"status": "SETTLED",
"on_chain_status": "",
"client_withdrawal_request_id": null,
"requested_timestamp": 1727921516403,
"transaction_id": "HND48J83HGR55LLP",
"input_data": null,
"fee_amount": "",
"quoted_fee_amount": null,
"quoted_fee_notional": null,
"trade_id": null,
"quoted_fee_asset": null,
"withdrawal_fee": "",
"parent_link_id": null,
"parent_link_id_source": null
}
]
}
Updated 24 days ago