Funding Models

zerohash supports three ACH funding models: Pre-funded, On-demand, and Instant USD. The model you choose determines funds availability for your customers and balance requirements you may have to begin processing.

Three Funding Models

You will have a choice between our three available funding models:

Funding ModelExplanationFloat?Loss Reserve?
On-demandTrades execute instantly once the ACH debit is approved (buy) or sale is initiated (sell), rather than waiting for settlement. A platform-funded float enables this, with your float balance determining how much your customers can trade.
Pre-fundedCustomers must fund their account before trading. Fund availability occurs after ACH settlement plus a brief holding period to account for ACH return risk. Withdrawals work the opposite way: customers sell crypto, then they can withdraw from their USD balance.
Instant USDCustomers USD balances are credited immediately upon ACH debit approval. They can trade with those USD balances before the ACH settles. Platform float funds the balances and determines how much your customers can trade.

Compatibility of funding models, liquidity models and trading engines:

Funding ModelCLOB?RFQ + Novated?RFQ + Direct?
On-demand
Pre-funded
Instant USD

On-demand

On-demand Integration Guide

After you Link a bank account, you can request crypto buys and sells. Crypto buys debit the end customer bank account, while crypto sells credit the end customer bank account. The platform float is leveraged to exchange crypto while the fiat leg of the transaction settles.

ℹ️ Platforms can be on either the Novated or Direct liquidity model to initiate on-demand crypto buys and sells via ACH.

Request an On-demand payment

POST /payments/rfq

Follow the same requirements as GET /liquidity/rfq. Sells are ACH or RTP credits and Buys are ACH debits.

The quote duration will default to your platform setting, as configured with Client Services. We recommend a one minute quote with this endpoint to accommodate required balance and authorization checks for the ACH payment. However, you may pass in the optional field quote_expiry with RFQ to override your default.

Request parameters

ParameterDescriptionRequired/Optional
sideThe side of the quote buy or sell.Required
underlying_currencyThe underlying asset for the quote.Required
quoted_currencyThe quoted asset for the quote.Required
quantityThe amount of the underlying currency. Either quantity or total must be provided.Optional
totalOnly for Buy: The desired amount of the quoted_currency for the quote. Either quantity or total must be provided.Optional
participant_codeThe participant that is requesting to buy/sell. Can be the platform's code or the customer's.Optional
account_labelThe account label associated with the account.Optional
📘

Use only quantity for Sell

When performing a quote to sell crypto, use quantity field and the total amount not rounded with all the decimals. This will avoid not enough credit errors when executing the RFQ.

Response parameters

ParameterDescriptionType
request_idThe identifier of the RFQstring
participant_codeThe identifier of the participant making the quote requeststring
quoted_currencyThe asset code for the quoted currency, e.g.USDstring
sideThe participant side of the quote -buy or sellstring
quantityThe amount of the quoted currencystring
priceThe cost per unit of underlying currencystring
quote_idThe identifier for the quote
Note: this is required to execute the quote
string
expire_tsTimestamp when the quote will expiretimestamp
account_groupThe group that the account is a part ofstring
account_labelThe account label associated with the accountstring
obo_participanton behalf of participant is the details of the participant benefiting the trade if not the submitterobject
network_fee_notionalfee notional in the currency quoted on the RFQstring
network_fee_quantityfee quantity in the underlying assetstring
total_notionalThe calculation:
( price * quantity ) + ( network_fee_notional + network_fee_quantity )
string
underlyingThe asset code for the underlying currency, e.g. BTCstring

Execute quote

POST /payments/execute

Prerequisite

ParameterDescriptionType
participant_codeThe code of the participant who wants to create a new ACH transaction, required.string
external_account_idThe unique identifier that zerohash generates to identify the external account. Received from POST /payments/external_accounts, required.string
quote_idThe unique identifier assigned to the quote that was executed with POST /payments/rfq, required.string
descriptionDescriptor that travels with the ACH transaction and is intended to show on the participant bank statement.
e.g., “COMPANY1”, please use something that helps the customer identify the transaction.
Customer statements will show: [Zero Hash] [ZH contact number] [Description].
Note: It is ultimately up to the receiving bank to determine what is shown on the bank statement. Some may ultimately not show the description 10 character limit.
string
ach_signed_agreementThe time at which the participant agreed to the ACH disclosures.timestamp

Sample response

{
  "message": {
    "request_id": "14f8ebb8-7530-4aa4-bef9-9d73d56313f3",
    "quote": {
      "request_id": "ce819fe8-b1d7-43bb-961c-e09ede0988d3",
      "participant_code": "CUST01",
      "quoted_currency": "USD",
      "side": "BUY",
      "quantity": "1",
      "price": "11430.90",
      "quote_id": "5cd07738b861c31e3bd61467BTC1Buy1568311644602",
      "expire_ts": 1568311649602,
      "account_group": "GRP001",
      "account_label": "sub_account_test",
      "obo_participant": {
        "participant_code": "20XRLH",
        "account_group": "WRD1K0",
        "account_label": "general"
      },
      "network_fee_notional": "1",
      "network_fee_quantity": "1",
      "total_notional": "2.00",
      "underlying": "BTC",
      "asset_cost_notional": "2.00"
    },
    "trade_id": "ba97133e-ab15-4c86-86c1-86671b8420bc",
    "status": "Completed",
    "ach_details": {
      "inbound_reference_id": "string"
    },
    "payment_amount": "string",
    "external_account_id": "0f68333e-2114-469d-b505-c850d776e063",
    "quote_id": "ba97133e-ab15-4c86-86c1-86671b8420bc",
    "description": "PLATFORM",
    "ach_signed_timestamp": 1561996924964
  }
}

Check payment status

GET /payments/status

Shares the current status of a debit or credit payment. Platforms should use this endpoint to understand funds availability and make their own decisions on how/when to make crypto available to end customers.

Understanding payment statuses follows the existing GET /payments/status flow. Sample response:

{
  "message": [
    {
      "transaction_id": "0f68333e-2114-469d-b505-c850d776e061",
      "participant_code": "ALI123",
      "amount": "12.01",
      "status": "posted",
      "transfer_type": "credit",
      "bank_transfer_id": "0f68333e-2114-469d-b505-c850d776e061",
      "trade_id": "0f68333e-2114-469d-b505-c850d776e061",
      "trade_status": "terminated",
      "velocity_status": "pending",
      "velocity_failed_rule": "participant_transactions(in_flight_transfers_by_participant)",
      "created_at": "1975-08-19T23:15:30.000Z",
      "updated_at": "1975-08-19T23:15:30.000Z"
    }
  ],
  "page": 1,
  "total_pages": 1,
  "page_size": 200,
  "count": 10
}

On-demand payment statuses

StatusDescriptionFinal?
submittedTransaction request has been received-
pending_tradeTransaction request has been approved, but the associated trade has not terminated.-
pendingThe associated trade has terminated and the fiat payment has been initiated-
postedTransaction has settled and cleared any applicable hold days. Transaction will be reconciled with internal ledger.-
settledTransaction is complete and funds have been credited to platform float✔️
canceledACH On-demand transactions can not be canceled✔️
failedTransaction request failed, no payment was initiated✔️
rejectedTransaction request was rejected due to velocity checks, no payment was initiated✔️
returnedTransaction request was initially approved but the ACH has returned unsuccessfully, before the transaction reached a 'Settled' state✔️
returned_settledTransaction request was initially approved but the ACH has returned unsuccessfully, after the transaction reached a 'Settled' state✔️
📘

Final states

States marked are all possible end-states for a transaction, though a transaction can move from settledreturned_settled

Pre-funded

Pre-funded Integration Guide

After you Link a bank account, you can request fiat deposits and withdrawals. Fiat deposits debit the end customer bank account and credit their zerohash USD account. Fiat withdrawals debit the end customer zerohash USD account and credit their bank account.

Platforms must be on the Direct or Default liquidity model to use fiat deposits and withdrawals.

Request a fiat deposit

POST /payments

Sample request

{
  'message': {
        'participant_code': 'ABC123',
        'external_account_id': '0f68333e-2114-469d-b505-c850d776e063',
        'currency': 'USD',
        'amount': '150.00',
        'description': 'COMPANY0',
        'transfer_type': 'debit'
  }
}

Sample response

{
  'message': {
    'request_id': '0f65678e-2114-469d-b505-c850d776e078',	
    'participant_code': 'ABC123',
    'external_account_id': '0f68333e-2114-469d-b505-c850d776e063',
    'currency': 'USD',
    'amount': '150.00',
    'description': 'COMPANY0',
    'transfer_type': 'debit',
    'transaction_id': '0f34533e-2114-469d-b505-c850d776e061',
    'status': 'submitted',
    'created_at': 1561996924964
  }
}

Buy crypto with a funded USD account

Pre-funded accounts leverage zerohash’s liquidity endpoints (GET /liquidity/rfq and POST /liquidity/execute). You may ignore sending an account_label as zerohash knows to default to the general account, as assigned when you reached POST /payments.

Sell crypto for USD

Selling acquired assets for USD follows the GET /liquidity/rfq and POST /liquidity/execute flows. Please ignore the funding_details and ach_details mentions. These are only applicable for a deprecated zerohash product.

Withdraw USD to bank account

Withdrawing USD to a customer bank account follows the POST /payments flow, where transfer_type = credit.

Check payment status

GET /payments/status

Shares the current status of a debit or credit payment. Platforms should use this endpoint to understand funds availability and make their own decisions on how/when to make funds available to end customers.

Understanding payment statuses follows the existing GET /payments/status flow.

Request parameters

StatusDescription
submittedRequest received and validated
pending_tradeTrade associated with the trade id is not terminated
pendingBalance and authorization confirmed with Plaid and transaction initialized
postedWithdrawn from end customer account
settledHold period is over and funds have moved to the bank
cancelledTransaction was proactively cancelled
failedTransaction failed during the process
returnedTransaction was returned prior to settlement (reason listed in the reason_code and reason_description of the webhook - most often “insufficient funds”)
returned_settledTransaction was returned after settlement (reason listed in the reason_code and reason_description of the webhook)
rejectedTransaction request blocked for risk mitigation or contractual reasons

Sample response

{
  "message": [
    {
      "transaction_id": "0f68333e-2114-469d-b505-c850d776e061",
      "participant_code": "ALI123",
      "amount": "12.01",
      "status": "posted",
      "transfer_type": "credit",
      "bank_transfer_id": "0f68333e-2114-469d-b505-c850d776e061",
      "trade_id": "0f68333e-2114-469d-b505-c850d776e061",
      "trade_status": "terminated",
      "velocity_status": "pending",
      "velocity_failed_rule": "participant_transactions(in_flight_transfers_by_participant)",
      "created_at": "1975-08-19T23:15:30.000Z",
      "updated_at": "1975-08-19T23:15:30.000Z"
    }
  ],
  "page": 1,
  "total_pages": 1,
  "page_size": 200,
  "count": 10
}

Pre-funded payment statuses

StatusDescriptionFinal?
submittedTransaction request has been received-
pendingTransaction request has been approved and payment has been initiated-
postedTransaction has settled and cleared any applicable hold days. Transaction will be reconciled with internal ledger.-
settledTransaction is complete and funds have been made available to participant✔️
canceledTransaction request has been canceled, no payment was initiated✔️
failedTransaction request failed, no payment was initiated✔️
rejectedTransaction request was rejected due to velocity checks, no payment was initiated✔️
returnedTransaction request was initially approved but the ACH has returned unsuccessfully, before the transaction reached a 'Settled' state✔️
returned_settledTransaction request was initially approved but the ACH has returned unsuccessfully, after the transaction reached a 'Settled' state✔️
📘

Final states

States marked are all possible end-states for a transaction, though a transaction can move from settledreturned_settled

Instant USD

Instant USD Integration Guide

Platforms must use zerohash Powered Plaid Link or Self Service Plaid Link to verify and link end customer bank accounts. Once an external account has been created and linked to a Participant account, Platforms can make ACH deposited funds instantly available to the end customer using zerohash Instant USD.

Request an Instant fiat deposit

POST /fund/deposit

Sample request

{
  'message': {
        'participant_code': 'ABC123',
        'external_account_id': '0f68333e-2114-469d-b505-c850d776e063',
        'asset': 'USD',
        'amount': '150.00',
          'description': 'COMPANY0',
              'network': 'ach',
              'instant': true
  }
}

Sample response

{
  'message': {
    'request_id': '0f65678e-2114-469d-b505-c850d776e078',	
    'participant_code': 'ABC123',
    'external_account_id': '0f68333e-2114-469d-b505-c850d776e063',
    'asset': 'USD',
    'amount': '150.00',
    'description': 'COMPANY0',
    'transaction_id': '0f34533e-2114-469d-b505-c850d776e061',
    'status': 'submitted',
    'created_at': 1561996924964
  }
}

Available balance

It can take 3-5 days for ACH transactions to settle. In the meantime, zerohash offers end customers an immediate available balance to make trades. In the Instant ACH flow, funds are immediately transferred from the platform float to the participants available balance. The platform can expect to receive a transfer webhook with the following information:

{
    "account_group": "F12JE6",
    "account_label": "general",
    "account_type": "available",
    "asset": "USD",
    "balance": "5",
    "movements": [
        {
            "account_id": "efeb40fe-aaf1-4132-8fbc-036e7612f7db",
            "change": "5",
            "movement_id": "b96daa8d-960a-4c50-acc1-af9c1c9c178b",
            "movement_timestamp": 1746112183373,
            "movement_type": "transfer",
            "transfer_request_id": "339910",
            "transfer_type": "trade_allocation"
        }
    ],
    "participant_code": "45JM2Q",
    "run_id": "4150531",
    "run_type": "transfer",
    "timestamp": 1746112183373
}

Buy crypto with USD Instantly

End customers are immediately able to purchase crypto using the available balance in their participant account with zerohash’s CLOB liquidity endpoints or with RFQ endpoints (depending on the trading model leveraged).

Crypto buy orders will settle immediately even if the ACH deposit has not settled. Platforms will receive web hooks for both events that look like this:

ACH deposit settlement

zerohash returns a webhook with 'status': 'settled' when an ACH deposit clears.

{
  'message': {
    'request_id': '0f65678e-2114-469d-b505-c850d776e078',	
    'participant_code': 'ABC123',
    'external_account_id': '0f68333e-2114-469d-b505-c850d776e063',
    'asset': 'USD',
    'amount': '150.00',
    'description': 'COMPANY0',
    'transfer_type': 'debit',
    'transaction_id': '0f34533e-2114-469d-b505-c850d776e061',
    'status': 'settled',
    'created_at': 1561996924964
  }
}

Crypto Buy Settlement

zerohash returns a webhook with 'trade_state': 'terminated' when an buy order settles. Find more information on trade webhooks here.

{
    "client_trade_id": "b3fbcf52-95f9-4624-9309-4f1306bdb176",
    "contract_size": "1",
    "market_identifier_code": "SCXM",
    "origin": "liquidity",
    "parties": [
        {
            "account_label": "general",
            "amount": "10",
            "asset": "USD",
            "participant_code": "PART1",
            "settling": true,
            "side": "sell"
        },
        {
            "account_label": "inventory",
            "amount": "0.0057236",
            "asset": "ETH",
            "participant_code": "00SCXM",
            "settling": true,
            "side": "buy"
        }
    ],
    "parties_anonymous": false,
    "physical_delivery": true,
    "platform_code": "00SCXM",
    "product_type": "spot",
    "quoted_currency": "USD",
    "symbol": "ETH/USD",
    "timestamp": 1745554838289,
    "total_notional": "10.00",
    "trade_id": "26d68f16-f9d2-4ab3-847e-bccf6f03a35e",
    "trade_price": "1747.1521420085261025",
    "trade_quantity": "0.0057236",
    "trade_reporter": "00SCXM",
    "trade_reporter_code": "00SCXM",
    "trade_state": "terminated",
    "trade_type": "regular",
    "transaction_timestamp": 1745554838000,
    "underlying": "ETH"
}
{
  'message': {
    'request_id': '0f65678e-2114-469d-b505-c850d776e078',	
    'participant_code': 'ABC123',
    'external_account_id': '0f68333e-2114-469d-b505-c850d776e063',
    'asset': 'USD',
    'amount': '150.00',
    'description': 'COMPANY0',
    'transaction_id': '0f34533e-2114-469d-b505-c850d776e061',
    'status': 'submitted',
    'created_at': 1561996924964
  }
}

Instant USD payment statuses

StatusDescriptionFinal?
submittedTransaction request has been received-
pending_tradeTransaction request has been approved, but the associated trade has not terminated.-
pendingThe associated trade has terminated and the fiat payment has been initiated-
postedTransaction has settled and cleared any applicable hold days. Transaction will be reconciled with internal ledger.-
settledTransaction is complete and funds have been credited to platform float✔️
canceledACH On-demand transactions can not be canceled✔️
failedTransaction request failed, no payment was initiated✔️
rejectedTransaction request was rejected due to velocity checks, no payment was initiated✔️
returnedTransaction request was initially approved but the ACH has returned unsuccessfully, before the transaction reached a 'Settled' state✔️
returned_settledTransaction request was initially approved but the ACH has returned unsuccessfully, after the transaction reached a 'Settled' state✔️
📘

Final states

States marked are all possible end-states for a transaction, though a transaction can move from settledreturned_settled

Account Types

Loss Reserve

For ACH, platforms fund a loss reserve balance to absorb any losses tied to ACH returns, which can occur up to 60 days after a debit has settled. It applies across all ACH debit flows.

RTP payments do not require a loss reserve, as RTP transactions are final once settled.

Reserve sizing is set by our financial risk team based on your expected volumes and use case.


Float Account

Float balances are funded to ensure funds can be made available to customers before the underlying payment settles. Whether it is required depends on the model:

  • On-demand and Instant USD: a float balance funds a user's trade or balance before their ACH deposit settles. A typical starting point for funding a float is 3–4 days of transaction volume, with additional headroom for volume spikes.
  • RTP: because RTP operates 24/7/365, an RTP float balance ensures that credits can be completed outside of banking hours. Sizing follows the same approach as On-demand: a buffer covering several days of volume, adjusted for expected spikes.

No float balance is required for Pre-funded, since in this model funds are not made available to a user until the ACH transfer settles, and a brief holding period has lapsed.


Webhook notifications

Webhooks let you know the status of a payment and if funds are available to the end customer or platform. The payload is a JSON object containing the following fields:

ParameterDescriptionType
participant_codeThe zerohash identifier for the customer requesting a Fiat transactionstring
typeIndicates if the payment is a credit or a debit for the end customerstring
transaction_idThe unique identifier generated by zerohash for the transactionstring
payment_statusThe current status of the payment. See payment statuses for morestring
reason_codeThe NACHA failure reason code (for ACH), if the payment_status is returnedstring
reason_descriptionThe description matching the reason_code, if the payment_status is returnedstring
expected_settlement_dateFor payment type of credit, when the funds are expected to settle to the end customer bank account. This is an approximation as receiving banks are ultimately responsible for posting the updatestring; YYYY-MM-DD format

For more information on the payments webhooks, check out our documentation here.


ACH returns

ACH returns are available in GET /payments/status under the status returned. While webhooks share the reason for returns, currently the GET call does not include this information.

Platforms must maintain a funded loss reserve and float account. When an ACH deposit fails, the participant account’s balance and buying power will be deducted accordingly. If buying power has already been used to initiate a crypto buy order, the pending order will be canceled. Losses will be recovered from the Platform.

  • If an ACH fails before settlement and buying power has been used to purchase an asset, zerohash will seize it from the participant account, liquidate it, and recover any losses from the Platform’s float account.
  • If an ACH is returned after settlement and funds cannot be recovered from the participant account, zerohash will recover losses from the Platform’s loss reserve account.

Unauthorized returns

An unauthorized return means that the bank account owner has flagged an issue with the transaction. When these returns are received, zerohash immediately locks the end customer account in an effort to mitigate further risk. The zerohash compliance team must evaluate risk before moving the participant back to approved, or along to disabled (more on participant statuses).

Participant status webhooks alert platforms to movements from approved to locked. In the case of unauthorized returns, those results share a reason of ach.


Did this page help you?