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.
Overview
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.
Acquire an onboarding access token by making an API call to the ZeroHash API server.
Invoke web flow by executing a Javascript function made available through the Zero Hash Web SDK and passing the onboarding access token.
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 |
---|---|---|
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: "customer@email.com",
phone_number: "33512345678"
}
const timestamp = Math.round(Date.now() / 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')
// SET HEADERS
const headers = {
'X-SCX-API-KEY': 'public_key',
'X-SCX-SIGNED': signedPayload,
'X-SCX-TIMESTAMP': timestamp,
'X-SCX-PASSPHRASE': 'passphrase'
}
const options = {
headers,
body,
json: true
}
return request.post(`https://api.zerohash.com/participants/onboarding_token`, options)
}
Sample Response
{
"message": {
"email": "customer@email.com",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
}
}
Step 2. Invoke web flow
Installation methods
Option 1: Download Release Bundle
- Download the Zero Hash web SDK release bundle:
- 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
- Add the following code to your HTML
<head>
section:<script type="module" src="zh_web_sdk/dist/index.js"></script>
- The Zero Hash SDK class will be available at window.zerohash
- Note: The
<script>
type
property must bemodule
.
Option 2: Direct CDN Reference
Will be implemented in a future release.
Option 3: npm package (recommended)
- cd to your node/npm project
- Run
npm i zh-web-sdk
Quick Setup
Follow the adjacent specifications. * Note: For Quick Setup in the Cert environment, use “https://onboarding.cert.zerohash.com/”
import ZeroHashSDK from "zh-web-sdk";
// Initialize SDK
const sdk = new ZeroHashSDK({
zeroHashOnboardingURL: "https://onboarding.zerohash.com/"
});
// Set the user onboarding JWT retrieved for a particular user
// A user onboarding JWT before the user can proceed with the onboarding flow.
sdk.openOnboardingModal({
userOnboardingJWT: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ." +
"SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
});
Verification results
Webhooks
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:
- We don't recommend polling as the primary method for getting verification results, but rather as a secondary check or failsafe
- If for some reason the webhook request fails, Zero Hash will re-attempt with an exponential backoff up to 10 times
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.
Webhooks
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 |
Examples
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 |
- The value of should
x-zh-hook-payload-type
beparticipant_status_changed
for this use of webhooks.
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:
18.189.25.175/32
3.18.218.32/32
3.22.145.85/32
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:
Python
import hashlib
import hmac
def verify_signature(payload_body, secret_token, signature_header) -> bool:
hash_object = hmac.new(secret_token.encode('utf-8'), msg=payload_body, digestmod=hashlib.sha256)
expected_signature = "sha256=" + hash_object.hexdigest()
return hmac.compare_digest(expected_signature, signature_header)
TypeScript
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)
.update(JSON.stringify(payload_body))
.digest("hex");
return `sha256=${signature}` === signature_header;
};
Golang (RSA security method)
Full example available at Link to Go playground.
import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
)
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
}