NAV Navbar

Zero Hash KYC as a service

Customer Verification

To take advantage of this add-on product, platforms must talk to the Zero Hash sales team.


Zero Hash’s customer verification (KYC) web client SDK enables secure collection and verification of customer data, without requiring the platform to integrate to a third party KYC vendor or handle sensitive data.

An onboarding access token, retrieved via an API call, is required for you to invoke the web SDK. The onboarding flow can then be invoked via a Javascript call, with the acquired onboarding access token.

  1. Acquire an onboarding access token by making an API call to the ZeroHash API server.

  2. Invoke web flow by executing a Javascript function made available through the Zero Hash Web SDK and passing the onboarding access token.

  3. Wait for a webhook response on your customer’s KYC status with Zero Hash.

Step 1. Acquire an onboarding access token

POST /participants/onboarding_token

To invoke the web SDK, you must request a temporary onboarding access token for each KYC verification event. This should be done in advance of rendering the onboarding page.

Generating an onboarding access token must be initiated on the server side. You must provide an <email> query parameter for the customer who will undergo KYC. This enables Zero Hash to uniquely identify participants and create a tie between our system and yours.

All API requests are authenticated using your signed API key. For detailed API usage, please refer to our online documentation.

Request body:

Parameter Description Type
email Customer email address, required Note: Zero Hash will validate that the email is a correctly formatted email, and that the value is unique per-platform string
phone_number The phone number of the participant, optional string

Additional fields in response:

Parameter Description Type
token Onboarding access token which gives the end customer permission to go through KYC string

Possible responses:

Status code Description
200 The JWT generated successfully
400 Not found Platform does not exist
403 Forbidden Invalid request or platform is not configured by Zero Hash for this KYC track

Sample request

const postOnboardingToken = () => {
  const body = {
    email: "",
    phone_number: "33512345678"
  const timestamp = Math.round( / 1000)
  const payload = timestamp + 'POST' + '/participants/onboarding_token' + JSON.stringify(body)
  const decodedSecret = Buffer.from(apiSecret, 'base64')
  const hmac = crypto.createHmac('sha256', decodedSecret)
  const signedPayload = hmac.update(payload).digest('base64')

  const headers = {
    'X-SCX-API-KEY': 'public_key',
    'X-SCX-SIGNED': signedPayload,
    'X-SCX-TIMESTAMP': timestamp,
    'X-SCX-PASSPHRASE': 'passphrase'
  const options = {
    json: true

  return``, options)

Sample Response

  "message": {
      "email": "",
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"

Step 2. Invoke web flow

Installation methods

Option 1: Download Release Bundle

  1. Download the Zero Hash web SDK release bundle:
  2. Copy index.js to somewhere in your code where it is public and accessible
    • Alternatively you may include it in your build process with other frontend assets
  3. Add the following code to your HTML <head> section: <script type="module" src="zh_web_sdk/dist/index.js"></script>
  4. The Zero Hash SDK class will be available at window.zerohash

Option 2: Direct CDN Reference

Will be implemented in a future release.

  1. cd to your node/npm project
  2. Run npm i zh-web-sdk

Quick Setup

Follow the adjacent specifications. * Note: For Quick Setup in the Cert environment, use “”

import ZeroHashSDK from "zh-web-sdk";

// Initialize SDK
const sdk = new ZeroHashSDK({
    zeroHashOnboardingURL: ""

// Set the user onboarding JWT retrieved for a particular user
// A user onboarding JWT before the user can proceed with the onboarding flow.
    userOnboardingJWT: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
        "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ." +

Verification results


Zero Hash pushes KYC results via a webhook.

In order to accommodate, Zero Hash needs a return URL where we can send your results. As of today, these are configured manually by Zero Hash until we enable a developer interface.

See Webhook documentation for more information.

Manual check

If you want to manually check the participant status, you can always acquire an up-to-date state of the participant via: GET /participants/:email

Keep in mind:

Design and placement

For the best user experience, try to initialize the SDK in the center of the screen for desktop browsers, or in a new tab for mobile browsers.

The Zero Hash SDK iframe resizes according to the container, so your container does not need to have a static size.


Currently, a return webhook is configured by contacting Zero Hash directly. Please reach out via the platform Slack channel, or directly to your Zero Hash relationship manager, with the URLs to direct production (Prod) and certification (Cert) webhooks. Webhooks are configured within 2 business days of receiving the URL.

Participant status changes

Platforms leveraging Zero Hash’s customer verification product should subscribe to webhooks to know if participants have been approved or rejected. This webhook can also be used by all platforms to understand if participants’ statuses have changed (i.e. if the participant has been locked or disabled).

Once a platform is set up to receive participant status webhooks, the payload is a JSON object containing the following fields:

Parameter Description Type
participant_code The Zero Hash identifier for the new customer Note: this value is key to enable you to submit trades and check account balances for this customer string
participant_status The status of the participant, which dictates whether they can trade or not. e.g. approved, rejected, locked, disabled, divested, closed string
reason_code If applicable, the reason the participant is in a status of locked, disabled, closed, or divested; e.g. compliance_issue, user_request string
timestamp The UNIX timestamp (in milliseconds) representing when the status was changed timestamp


KYC approved

  "participant_code": "ABC123",
  "participant_status": "approved",
  "timestamp": 1670958435349

KYC failed

  "participant_code": "ABC123",
  "participant_status": "rejected",
  "timestamp": 1670958435349

Participant status change

  "participant_code": "ABC123",
  "participant_status": "locked",
  "reason_code": "compliance_issue",
  "timestamp": 1670958435349

In addition to the JSON body, we also include headers:

Name Description
x-zh-hook-notification-id Notification ID, can be used for idempotency checks
x-zh-hook-payload-type Payload type string

Depending on your security configuration, additional headers may also be included:

Name Description
x-zh-hook-signature-256 to_hex(hmac(sha_256(payload), your-secret))
x-zh-hook-rsa-signature-256 to_hex(rsa(sha_256(payload), zh-sec-key))

Zero Hash’s webhook requests will originate from the following IP addresses, should you need an allow-list:

Webhook Security

To ensure the authenticity of the webhook, you may provide a secret token to us during configuration. If you’ve done so, we will include the x-zh-hook-signature-256 header with the webhook.

See these code examples to validate the signature:


import hashlib
import hmac

def verify_signature(payload_body, secret_token, signature_header) -> bool:
    hash_object ='utf-8'), msg=payload_body, digestmod=hashlib.sha256)
    expected_signature = "sha256=" + hash_object.hexdigest()

    return hmac.compare_digest(expected_signature, signature_header)


import * as crypto from "crypto";

const WEBHOOK_SECRET: string = process.env.WEBHOOK_SECRET;

const verify_signature = (payload_body, secret_token, signature_header) => {
  const signature = crypto
    .createHmac("sha256", secret_token)
  return `sha256=${signature}` === signature_header;

Golang (RSA security method)

Full example available at Link to Go playground.

import (

func verifySignature(r *http.Request, pub *rsa.PublicKey) error {
    // Calculate payload sha256 hash.
    hash, err := calculatePayloadHash(r)
    if err != nil {
        return fmt.Errorf("calculate hash: %w", err)

    // Get request signatue, signature is hex encoded.
    const zhWebhookRSASecHeader = "x-zh-hook-rsa-signature-256"
    requestSignature, err := hex.DecodeString(r.Header.Get(zhWebhookRSASecHeader))
    if err != nil {
        return fmt.Errorf("decode header: %w", err)

    // Verify signatiure
    err = rsa.VerifyPSS(pub, crypto.SHA256, hash, requestSignature, nil)
    if err != nil {
        return fmt.Errorf("verify PSS: %w", err)

    return nil

func calculatePayloadHash(r *http.Request) ([]byte, error) {
    payload, err := io.ReadAll(r.Body)
    if err != nil {
        return nil, fmt.Errorf("read body: %w", err)

    h := sha256.New()
    _, err = h.Write(payload)
    if err != nil {
        return nil, fmt.Errorf("write sha buffer: %w", err)

    return h.Sum(nil), nil