Payins API Integration Guide

See core product page here.

Definitions

TermDefinition
PlatformThe company that is under contract with zerohash and integrates directly with the zerohash's API
MerchantA business entity (non-natural person) that serves as the customer-facing front-end in the transaction flow. Typically, the Merchant is a customer of the Platform and is where the Shopper initiates their interaction. In some cases, the Merchant and Platform may be the same entity.
ShopperThe natural person that pays for the good or service

How the Integration Works

A high-level summary of each step of the Payins API integration

StepNotes
Merchant onboardingOnboard the merchants that sell goods and services through your platform. Only applicable when Platform is not acting as the Merchant (ex. PSPs that acquire merchants)
Shopper onboardingOnboard Shoppers making their first crypto or stablecoin purchases
Initiate Shopper's paymentGenerate deposit instructions for Shoppers
Initiate refundLink external account where Shopper will receive funds and facilitate repayment

1. Submit Merchant

Merchant Information

ℹ️

This section is only applicable if the Platform is not also acting as the Merchant. Typically Payment Service Providers (PSP's) who acquire merchants will need to submit Merchants to zerohash.

Begin by submitting the Merchant via POST /participants/entity/new

{
    "platform_code": "PLAT01",
    "entity_name": "Merchant XYZ",
    "legal_name": "Merchant XYZ 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",
    "submitter_last_name": "Doe",
    "submitter_title": "Senior Legal Council",
    "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 merchant indefinitely. We’ll use MERCH1 as the participant_code throughout the examples.

See response for expected shape.

You must submit at least 1 beneficial_owners and 1 control_persons. If these persons do not have an SSN, you must submit an ID 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.

Next, submit the Merchants 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": "MERCH1"
}

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, you must then submit all required documents via POST /participants/entity/documents.

2. Query Merchant

You can query already-submitted Merchants via the GET /participants endpoint. If you'd like to query a specific Merchant, use theparticipant_code parameter.

3. Fund Refund Account

To enable future refunds, you must pre-fund your Refund account by sending a wire transfer to the designated bank account provided by our settlement team. Ensure the wire memo is tagged with your Platform code + "_refund" (ie, PLAT01_refund):

Here are the account details:

  • Participant_code: 00SCXM
  • Account_group: PLAT01 (replace with your participant_code)
  • Account_label: pay_refund
  • Account_type: available
  • Asset: USD

4. Submit Shopper

Next, you'll need to onboard the end users (ie, "Shoppers"). Depending on each Shopper's transaction amount, overall transaction volume, and residence, we have differing level of KYC requirements. See table below:

If a Shopper’s total payment volume exceeds $999 within a 24-hour period, you must meet Tier 2 requirements before any further payments can be processed.

To submit a shopper, send a request to the POST /participants/customers/new endpoint.

Tier 1 Submission Example

Example POST /participants/customers/new request:

{
    "first_name": "John",
    "last_name": "Smith",
    "address_one": "1 Main St.",
    "address_two": "Suite 1000",
    "zip": "10014",
    "city": "New York City",
    "jurisdiction_code": "US-NY",
    "phone_number": "123456789",
    "date_of_birth": "1992-01-10",
    "partial": "true",
		"signed_agreements": [
    {
      "type": "user_agreement",
      "region": "us",
      "signed_timestamp": 1603378501286
      },
  ],

}
📘

Signature Collection

For the Payins API Solution, it is critical that you collect consent from the Shopper to zerohash's user agreements and share confirmation that consent was collected through the POST /participants/customers/new endpoint.

5. Query Shopper

To retrieve general information about the Shopper, query the GET /participants endpoint. You can use the participant_code filter to retrieve an individual Shopper.


6. Query Shopper Limits

To retrieve transaction limits for a Shopper's tier, query the GET /pay/limits endpoint. You can use the participant_code filter to retrieve an individual Shopper. Example response:

{
    "participant_code": "SHOPP1",
    "limits": [{
            "type": [
                "payment"
            ],
            "period": 24,
            "balance":"100"
            "limit": "999",
            "asset": "USD"
      }
    ]
}

Interpretation of the above: over the last 24 hours, the Shopper has done $100 of payment activity.

7. Accounts

The Platform will have the following accounts at zerohash:

AccountDescriptionTechnical Details
Settlement AccountThe fiat account that contains the balance of successful payments- participant_code = [your platform code] - account_group = [your platform code] - account_label = general - account_type = available - asset = USD (or other fiat currency being used)
Refund Float AccountThe fiat account, funded by the Platform, that is used to refund Shoppers. The balances here will be converted to a crypto or stablecoin asset and sent on-chain to the Shopper. See section, "11. Refund a Payment" below for details- participant_code = 00SCXM - account_group = [your platform code] - account_label = pay_refund - account_type = available - asset = USD (or other fiat currency being used)
Error AccountThe fiat account that accumulates due to error scenarios (over payment, under payment, incorrect asset or network, late payment- participant_code = [your platform code] - account_group = [your platform code] - account_label = pay_error - account_type = available - asset = USD (or other fiat currency being used)

8. Initiate Payment

Request a quote - POST /pay/rfq

Generate quote for the payment amount.

Example Request:

{
  "participant_code": "SHOPP1",
  "pay_asset": "USDC",
  "quoted_total": "100",
  "quoted_currency": "100",
}

Interpretation of this request - the Shopper (SHOPP1) is making a $100 payment to the Platform and/or Merchant using 100 of USDC as the asset.

Example Response:

{
 
    "request_id": "14f8ebb8-7530-4aa4-bef9-9d73d56313f3",
    "participant_code": "SHOPP1",
    "pay_asset": "USDC",
    "rate": "1",
    "quoted_currency": "1",
    "quoted_total": "100",
		"underlying_quantity":"100",
    "expiry_timestamp": 2554408627334,
    "deposit_address": "0x5f59B625036ccB4f7aD27Ca4Cb896e4452AfFDAF",
    "client_reference_id": "f2f14251-e296-42ac-9bc7-01c9186b921c",
    "is_status": true //required for stablecoins
  }


End customer initiates on-chain payment

On the Platform's user interface, the Shopper is presented with the deposit_address returned by POST /pay/rfq, and the amount to deposit. They use their Metamask wallet, for example, to trigger a withdrawal into the deposit_address for the amount of the payment.


Payment Successful

Successful Payment (exact amount)

Once a successful payment is received for the exact amount, we'll send a webhook to you that looks like this:

{
  "participant_code": "SHOPP1",
  "fund_asset": "USDC.BASE",
  "rate": "1",
  "quoted_currency": "USD",
  "source_address": "external",
  "deposit_address": "0xb07C48f0BF25A97034c818Dc643A98e42703D20c",
  "quantity": "100",
  "notional": "100",
  "fund_id": "6767a149-4771-4166-ba07-a2542e361318",
  "fund_timestamp": 1756376889792000000,
  "deposit_timestamp": 1756376889499000000,
  "transaction_id": "d1d731ed-952c-410e-b62b-eb00e3d096fc",
  "account_label": "general",
  "success": true,
  "reason": "",
  "reference_id": "04918aef-41d1-4dfb-82b8-2fdc232a8d88",
  "raw_fee_bps": "",
  "deposit_fee_bps": "",
  "raw_fee_notional": "",
  "deposit_fee_notional": ""
}

Successful Payment (over payment)

Once a successful payment is received where the Shopper sent too much, we'll send a webhook to you that looks like this:

{
  "participant_code": "SHOPP1",
  "fund_asset": "USDC.BASE",
  "rate": "1",
  "quoted_currency": "USD",
  "source_address": "external",
  "deposit_address": "0xb07C48f0BF25A97034c818Dc643A98e42703D20c",
  "quantity": "100",
  "notional": "100",
  "fund_id": "6767a149-4771-4166-ba07-a2542e361318",
  "fund_timestamp": 1756376889792000000,
  "deposit_timestamp": 1756376889499000000,
  "transaction_id": "d1d731ed-952c-410e-b62b-eb00e3d096fc",
  "account_label": "general",
  "success": true,
  "reason": "over payment",
  "reference_id": "04918aef-41d1-4dfb-82b8-2fdc232a8d88",
  "raw_fee_bps": "",
  "deposit_fee_bps": "",
  "raw_fee_notional": "",
  "deposit_fee_notional": ""
}

Payment Unsuccessful

Failure scenarioExpected remediation steps
Under paymentShopper to contact Platform → Platform to facilitate a withdraw via POST /payments to Shopper's wallet
DepegShopper to contact Platform → Platform to contact zerohash via Slack or Email to facilitate withdrawals back to the Shopper manually
Above max thresholdShopper to contact Platform → Platform to contact zerohash via Slack or Email to facilitate withdrawals back to the Shopper manually
QuarantineShopper to contact Platform → Platform to contact zerohash via Slack or Email. Note: zerohash will likely disable this Shopper, as this scenario is typically triggered due receiving a deposit from a high-risk source address
Late paymentShopper to contact Platform → Platform to facilitate a withdraw via POST /payments to Shopper's wallet
Wrong asset or networkShopper to contact Platform → Platform to facilitate a withdraw via POST /payments to Shopper's wallet

Payment Unsuccessful (under payment)

Upon a payment for an insufficient amount, a webhook notification will be triggered as follows

{
  "participant_code": "SHOPP1",
  "fund_asset": "USDC.BASE",
  "rate": "1",
  "quoted_currency": "USD",
  "source_address": "external",
  "deposit_address": "0xb07C48f0BF25A97034c818Dc643A98e42703D20c",
  "quantity": "90",
  "notional": "",
  "fund_id": "6767a149-4771-4166-ba07-a2542e361318",
  "fund_timestamp": 1756376889792000000,
  "deposit_timestamp": 1756376889499000000,
  "transaction_id": "d1d731ed-952c-410e-b62b-eb00e3d096fc",
  "account_label": "general",
  "success": false,
  "reason": "under payment",
  "reference_id": "04918aef-41d1-4dfb-82b8-2fdc232a8d88",
  "raw_fee_bps": "",
  "deposit_fee_bps": "",
  "raw_fee_notional": "",
  "deposit_fee_notional": ""
}

Successful Payment (depeg)

If a stablecoin becomes depegged, to mitigate zerohahs's risk, we will manually mark the asset as depegged on our side. This will block all “sell” transactions converting that stablecoin to USD. Should we receive a payment deposit during this period, we will not convert the deposit to fiat. Instead, the funds will be moved to a dedicated depeg account, and a webhook notification will be triggered as follows:

{
  "participant_code": "SHOPP1",
  "fund_asset": "USDC.BASE",
  "rate": "1",
  "quoted_currency": "USD",
  "source_address": "external",
  "deposit_address": "0xb07C48f0BF25A97034c818Dc643A98e42703D20c",
  "quantity": "100",
  "notional": "",
  "fund_id": "6767a149-4771-4166-ba07-a2542e361318",
  "fund_timestamp": 1756376889792000000,
  "deposit_timestamp": 1756376889499000000,
  "transaction_id": "d1d731ed-952c-410e-b62b-eb00e3d096fc",
  "account_label": "general",
  "success": false,
  "reason": "depeg",
  "reference_id": "04918aef-41d1-4dfb-82b8-2fdc232a8d88",
  "raw_fee_bps": "",
  "deposit_fee_bps": "",
  "raw_fee_notional": "",
  "deposit_fee_notional": ""
}

Above max threshold

zerohash has certain liquidation maximums on our end. If a large deposit is received above this threshold, a webhook notification will be triggered as follows:

{
  "participant_code": "SHOPP1",
  "fund_asset": "USDC.BASE",
  "rate": "1",
  "quoted_currency": "USD",
  "source_address": "external",
  "deposit_address": "0xb07C48f0BF25A97034c818Dc643A98e42703D20c",
  "quantity": "10000000",
  "notional": "",
  "fund_id": "6767a149-4771-4166-ba07-a2542e361318",
  "fund_timestamp": 1756376889792000000,
  "deposit_timestamp": 1756376889499000000,
  "transaction_id": "d1d731ed-952c-410e-b62b-eb00e3d096fc",
  "account_label": "general",
  "success": false,
  "reason": "depeg",
  "reference_id": "04918aef-41d1-4dfb-82b8-2fdc232a8d88",
  "raw_fee_bps": "",
  "deposit_fee_bps": "",
  "raw_fee_notional": "",
  "deposit_fee_notional": ""
}

Quarantine

If zerohash detects that the source address of the payment deposit is high-risk, a webhook notification will be triggered as follows:

{
  "participant_code": "SHOPP1",
  "fund_asset": "USDC.BASE",
  "rate": "1",
  "quoted_currency": "USD",
  "source_address": "external",
  "deposit_address": "0xb07C48f0BF25A97034c818Dc643A98e42703D20c",
  "quantity": "10000000",
  "notional": "",
  "fund_id": "6767a149-4771-4166-ba07-a2542e361318",
  "fund_timestamp": 1756376889792000000,
  "deposit_timestamp": 1756376889499000000,
  "transaction_id": "d1d731ed-952c-410e-b62b-eb00e3d096fc",
  "account_label": "general",
  "success": false,
  "reason": "For Zero Hash Compliance reasons, the deposit has not been converted to fiat and the crypto has been credited to a Zero Hash holding account",
  "reference_id": "04918aef-41d1-4dfb-82b8-2fdc232a8d88",
  "raw_fee_bps": "",
  "deposit_fee_bps": "",
  "raw_fee_notional": "",
  "deposit_fee_notional": ""
}

Late payment

If zerohash receives a payment deposit past the Payment Window, a webhook notification will be triggered as follows:

{
  "participant_code": "SHOPP1",
  "fund_asset": "USDC.BASE",
  "rate": "1",
  "quoted_currency": "USD",
  "source_address": "external",
  "deposit_address": "0xb07C48f0BF25A97034c818Dc643A98e42703D20c",
  "quantity": "10000000",
  "notional": "",
  "fund_id": "6767a149-4771-4166-ba07-a2542e361318",
  "fund_timestamp": 1756376889792000000,
  "deposit_timestamp": 1756376889499000000,
  "transaction_id": "d1d731ed-952c-410e-b62b-eb00e3d096fc",
  "account_label": "general",
  "success": false,
  "reason": "payment session has expired",
  "reference_id": "04918aef-41d1-4dfb-82b8-2fdc232a8d88",
  "raw_fee_bps": "",
  "deposit_fee_bps": "",
  "raw_fee_notional": "",
  "deposit_fee_notional": ""
}

Wrong asset or network

If zerohash receives a payment deposit for an asset that was not selected by the Shopper on the Select Asset page, a webhook notification will be triggered as follows:

{
  "participant_code": "SHOPP1",
  "fund_asset": "ETH",
  "rate": "1",
  "quoted_currency": "USD",
  "source_address": "external",
  "deposit_address": "0xb07C48f0BF25A97034c818Dc643A98e42703D20c",
  "quantity": ".15",
  "notional": "",
  "fund_id": "6767a149-4771-4166-ba07-a2542e361318",
  "fund_timestamp": 1756376889792000000,
  "deposit_timestamp": 1756376889499000000,
  "transaction_id": "d1d731ed-952c-410e-b62b-eb00e3d096fc",
  "account_label": "general",
  "success": false,
  "reason": "deposit currency does not match the expected currency",
  "reference_id": "04918aef-41d1-4dfb-82b8-2fdc232a8d88",
  "raw_fee_bps": "",
  "deposit_fee_bps": "",
  "raw_fee_notional": "",
  "deposit_fee_notional": ""
}

9. Query payments

You can query our REST endpoint GET /pay/transactions to retroactively view payment details.

Sample Response:

    {
      "participant_code": "SHOPP1",
      "fund_asset": "USDC",
      "quoted_currency": "USD",
      "deposit_address": "0xabc123usdcwalletaddress",
      "rate": "1.00",
      "quantity": "100.00",
      "notional": "100.00",
      "fund_timestamp": 1712756400,
      "transaction_id": "111e8400-e29b-41d4-a716-446655440000",
      "account_label": "pay",
      "fund_id": "fund-usdc-1001",
      "success": true,
      "status_reason": "",
      "status_reason_code": "",
      "is_first_deposit": true,
      "raw_fee_bps": "25",
      "actual_fee_bps": "25",
      "raw_fee_notional": "0.25",
      "actual_fee_notional": "0.25",
      "deposit_timestamp": 1712756700,
      "source_address": "0xsenderwallet123",
      "deposited_asset": "USDC",
      "client_reference_id": "order-1001",
      "source": {
        "source_type": "on_chain",
        "integration": "ethereum"
      },
      "deposit_fee_type": "DEPOSIT_FEE_TYPE_FLAT",
      "fee_tier_breakdown": []
    }
  ],
  "page": 1,
  "page_size": 50,
  "total_pages": 1
}

10. Settlement

zerohash will, one time per day, send you a fiat settlement wire where the amount represents the sum of all converted payments from the prior trading session. Here is the settlement schedule:

SessionStartEndExpected Settlement Time
MondayMonday 9:00a ESTTuesday 8:59:59a ESTTuesday EOD
TuesdayTuesday 9:00a ESTWednesday 8:59:59a ESTWednesday EOD
WednesdayWednesday 9:00a ESTThursday 8:59:59a ESTThursday EOD
ThursdayThursday 9:00a ESTFriday 8:59:59a ESTFriday EOD
FridayFriday 9:00a ESTMonday 8:59:59a ESTMonday 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)

11. Refund a Payment

In order to initiate a refund, you should use the following endpoints:

  • POST /payments/external_accounts - links a crypto account where the Shopper will receive their refund
  • POST /payments - initiates a conversion from USD (using Refund account) to the crypto asset. This API call will also automatically and immediately send the funds on-chain.
🚧

IMPORTANT: You cannot rely on the deposit’s source address as the refund destination. Blockchain transactions are not inherently bidirectional, and returning funds to the sender address may result in loss of funds or failed deliveries.

Some Platforms may choose to pair their zerohash integration with their own transaction monitoring tools. In situations where zerohash accepts a deposit, but the Platform’s monitoring logic flags and rejects it, the Platform may opt to return the deposit by using the POST /convert_withdraw/rfq and POST /convert_withdraw/execute endpoint.

The required flow is to always request and confirm a return address directly from the Shopper before initiating the return.

Please speak to a zerohash sales engineer for guidance if interested in this flow.