@zyphe-sdk/node
Install the package:
- pnpm
- npm
- Yarn
- Bun
pnpm add @zyphe-sdk/node
npm install --save @zyphe-sdk/node
yarn add @zyphe-sdk/node
bun add @zyphe-sdk/node
1. Webhook Signature Verification
This package provides the core webhook signature verification logic for Zyphe. It enables secure verification of incoming webhook requests in Node.js using HMAC-SHA256 signatures.
Basic Usage
import { verifyWebhookSignatureHeader } from '@zyphe-sdk/node'
const secretHex = '<your-hex-encoded-secret>'
const rawBodyString = '...' // The raw request body as a string
const signatureHeader = 't=1234567890,v0=abcdef...' // The signature header from the webhook
const isValid = verifyWebhookSignatureHeader(secretHex, rawBodyString, signatureHeader)
if (isValid) {
// Signature is valid
} else {
// Signature is invalid
}
secretHex: The hex-encoded secret key used to sign the webhook.rawBodyString: The raw request body as a string (must match exactly what was sent).signatureHeader: The signature header string, e.g.t=1234567890,v0=abcdef....- Returns:
trueif the signature is valid, orfalseotherwise.
Advanced Utilities
parseSignatureHeader(signatureHeader), Parse the signature header into timestamp and signature.hexToBytes(hex)/bufferToHex(buffer), Convert between hex and bytes.timingSafeEqual(a, b), Constant-time string comparison.- Error codes/messages for robust error handling.
2. Verification Session Management
You can programmatically create verification sessions and construct user-facing URLs for onboarding flows.
Create a Verification Session
import { createVerificationSession } from '@zyphe-sdk/node'
const response = await createVerificationSession(
{
// Provide at least one identity field: email or a credential.
email: 'user@example.com', // optional when credentials are provided
credentials: [
{ type: 'EXTERNAL_ID', externalId: 'user_123' },
{ type: 'WALLET', address: '0x123...', chain: 'ETHEREUM' },
], // optional when email is provided
flowId: '<flowId>', // required
isSandbox: false, // required
customData: {
// optional
plan: 'pro',
// ...any extra data you want to pass
},
},
{
// Secret key (zyphe_sk_...) — required for server-side session creation
apiKey: '<your-secret-api-key>',
// production → https://api.zyphe.com + https://verify.zyphe.com
// See Environment setup: /docs/guides/environment-setup
environment: 'production',
handoffBaseUrl: 'https://your-app.com', // optional
// Theming, all optional. Light mode:
backgroundColor: '#ffffff', // page background
accentColor: '#155dfb', // buttons, icons, highlights
cardColor: '#ffffff', // card surface
// Dark mode:
darkBackgroundColor: '#16181d',
darkAccentColor: '#155dfb',
darkCardColor: '#16181d',
borderRadius: '12px', // shared radius for cards, buttons, inputs
// Deprecated (still honored): primaryColor → background, secondaryColor → accent
isFullscreen: true, // optional
},
)
emailandcredentialsidentify the user. Provide at least one ofemail, anEXTERNAL_IDcredential, or aWALLETcredential.credentials(optional): Pass an array of credentials, for example{ type: 'EXTERNAL_ID', externalId: 'user_123' }or{ type: 'WALLET', address: '0x123...', chain: 'ETHEREUM' }.- You can combine identity fields, such as
emailplusexternalId,emailpluswallet, or all three. customData(optional): Pass any extra data you want to associate with the verification session. This will be available in the webhook payload and session data.- The function returns a Promise resolving to the API response. Handle errors as needed (e.g., try/catch or checking for error fields).
Construct a Verification Session URL
import { constructVerificationSessionUrl } from '@zyphe-sdk/node'
const url = constructVerificationSessionUrl({
flowParams: {
flowId: '...',
email: 'user@example.com',
credentials: [{ type: 'EXTERNAL_ID', externalId: 'user_123' }],
isSandbox: false,
customData: { plan: 'pro' }, // optional
},
verificationSession: response.data, // from createVerificationSession
opts: {
apiKey: '<your-api-key>',
environment: 'production',
handoffBaseUrl: 'https://your-app.com', // optional
// Theming, all optional. Light mode:
backgroundColor: '#ffffff', // page background
accentColor: '#155dfb', // buttons, icons, highlights
cardColor: '#ffffff', // card surface
// Dark mode:
darkBackgroundColor: '#16181d',
darkAccentColor: '#155dfb',
darkCardColor: '#16181d',
borderRadius: '12px', // shared radius for cards, buttons, inputs
// Deprecated (still honored): primaryColor → background, secondaryColor → accent
isFullscreen: true, // optional
},
})
- The constructed URL includes all relevant parameters and can be used to embed or redirect users to the verification flow.
- Optional fields (like
handoffBaseUrl,accentColor,borderRadius, etc.) will be appended as query parameters if provided. See Theming for the full list of theme options and how they work.
Types
InitializeZypheFlowParams: Parameters for initializing a verification flow.SDKOptions: Configuration for the SDK.SdkCreateVerificationRequestResponse: API response type for session creation.
Error Handling
- If session creation fails, the function may throw or return an error object depending on usage context. Always check for errors and handle them appropriately.
- Example:
try {
const response = await createVerificationSession(...);
if (response.error) {
// Handle error
console.error(response.error);
} else {
// Success
}
} catch (err) {
// Handle thrown errors (e.g., network issues)
console.error(err);
}
The email field in flowParams does not accept email aliases (e.g. user+tag@example.com) in production environments. Use a plain email address without +alias suffixes when isSandbox is false.
3. Complete Verification Steps
The Node SDK also includes helpers for completing document selection, document verification, and liveness steps from your backend.
Typical flow:
- Create a verification session with
createVerificationSession. - Check the current step with
getNextStepDetailsif you need to branch dynamically. - Complete document selection with
completeDocumentSelectionStepwhen the next step isdocument-selection. - Upload document images with
completeDvStepwhen the next step is a document verification step. - Complete liveness with
completeLivenessStepwhen the flow requires liveness.
Inspect the Next Step
import { getNextStepDetails, type SDKOptions } from '@zyphe-sdk/node'
const opts: SDKOptions = {
apiKey: '<your-secret-api-key>',
environment: 'production',
}
const { error, data: nextStep } = await getNextStepDetails(
{
flowId: '<flow-id>',
email: 'user@example.com',
credential: { type: 'EXTERNAL_ID', externalId: 'user_123' },
isSandbox: true,
},
opts,
)
if (error) {
console.error(error)
} else {
console.log('Next step:', nextStep)
}
- Use
getNextStepDetailsto decide whether to ask for document selection, document images, liveness capture, or another step. - Provide at least one identity field:
email, anEXTERNAL_IDcredential, or aWALLETcredential. - This helper calls the
sdk_get_next_stependpoint documented indocs/openapi/sdk-get-next-step.api.mdx.
Complete Document Selection and Document Verification
import { completeDocumentSelectionStep, completeDvStep, createVerificationSession, DocumentType, type SDKOptions } from '@zyphe-sdk/node'
import { readFile } from 'node:fs/promises'
async function readBase64File(filePath: string) {
return (await readFile(filePath)).toString('base64')
}
const opts: SDKOptions = {
apiKey: '<your-secret-api-key>',
environment: 'production',
}
const verificationSessionResponse = await createVerificationSession(
{
email: 'user@example.com',
flowId: '<flow-id>',
isSandbox: true,
},
opts,
)
if (verificationSessionResponse.error || !verificationSessionResponse.data) {
throw new Error('Failed to create verification session')
}
await completeDocumentSelectionStep(
{
verificationSession: verificationSessionResponse.data,
payload: {
country: 'IT',
documentType: DocumentType.IDENTITY_CARD,
},
},
opts,
)
const dvResult = await completeDvStep(
{
verificationSession: verificationSessionResponse.data,
countryCode: 'IT',
images: {
type: 'DOUBLE_SIDED_DOCUMENT',
frontImage: await readBase64File('./front.jpg'),
backImage: await readBase64File('./back.jpg'),
},
},
opts,
)
console.log('DV step completed')
console.log(dvResult)
- This example follows
../zyphe-sdk/examples/node-complete-step-dv/src/index.ts. - Use
DocumentType.IDENTITY_CARD,DocumentType.DRIVING_LICENSE, orDocumentType.PASSPORTwhen selecting the document type. completeDvStepaccepts either:type: 'DOUBLE_SIDED_DOCUMENT'withfrontImageandbackImagetype: 'SINGLE_SIDED_DOCUMENT'with a singleimage
- Image values must be base64-encoded strings.
- The SDK returns the API response for the submitted document job, including the verification request ID and job ID.
Complete a Liveness Step
import { completeLivenessStep, createVerificationSession, type SDKOptions } from '@zyphe-sdk/node'
import { readFile } from 'node:fs/promises'
async function readBinaryFile(filePath: string) {
return new Uint8Array(await readFile(filePath))
}
const opts: SDKOptions = {
apiKey: '<your-secret-api-key>',
environment: 'production',
}
const verificationSessionResponse = await createVerificationSession(
{
email: 'user@example.com',
flowId: '<flow-id>',
isSandbox: true,
},
opts,
)
if (verificationSessionResponse.error || !verificationSessionResponse.data) {
throw new Error('Failed to create verification session')
}
const calibrationImage = await readBinaryFile('./calibration.jpg')
const video = await readBinaryFile('./liveness.mp4')
const movementImages = await Promise.all(['./movement-1.jpg', './movement-2.jpg'].map((filePath) => readBinaryFile(filePath)))
const livenessResult = await completeLivenessStep(
{
verificationSession: verificationSessionResponse.data,
frontImage: Buffer.from(calibrationImage),
video: Buffer.from(video),
movementImages: movementImages.map((image) => Buffer.from(image)),
},
opts,
)
console.log('Liveness step completed')
console.log(livenessResult)
- This example follows
../zyphe-sdk/examples/node-complete-step-liveness/src/index.ts. completeLivenessStepfirst fetches the challenge configuration, uploads the required assets to the provided presigned URLs, and then calls the final liveness processing endpoint.- For active liveness challenges, you must provide a
videoand the same number ofmovementImagesrequested by the challenge. frontImageis the calibration image uploaded before the final liveness submission.
4. Flow Results
You can use the Node SDK to list flow results for a flow and fetch a single flow result by ID.
List Flow Results
import { listFlowResults, type SDKOptions } from '@zyphe-sdk/node'
const opts: SDKOptions = {
apiKey: '<your-secret-api-key>',
environment: 'production',
}
const { error, data } = await listFlowResults(
{
organizationId: '<organization-id>',
flowSlug: '<flow-slug>',
isSandbox: true,
pagination: {
take: 10,
skip: 0,
},
},
opts,
)
if (error) {
console.error(error)
} else {
console.log('Total flow results:', data?.totalCount ?? 0)
console.log('Current page:', data?.page ?? [])
}
organizationId: The organization that owns the flow results.flowSlug: The flow slug used to scope the results.isSandbox: Set totruefor sandbox data andfalsefor production data.pagination(optional): Usetake,skip,sort, andfilterto control paging.locale(optional): Localize fields that support translated values.- Returns: A Promise resolving to a paginated response with
totalCountand apagearray of flow results.
Get a Flow Result
import { getFlowResult, type SDKOptions } from '@zyphe-sdk/node'
const opts: SDKOptions = {
apiKey: '<your-secret-api-key>',
environment: 'production',
}
const { error, data: flowResult } = await getFlowResult(
{
organizationId: '<organization-id>',
flowResultId: '<flow-result-id>',
isSandbox: true,
},
opts,
)
if (error) {
console.error(error)
} else {
console.log('Flow result:', flowResult)
}
flowResultId: The ID of the specific flow result you want to retrieve.- Returns: A Promise resolving to the full flow result payload, including result collections such as
dvResults,poaResults, andamlResultswhen available.
getFlowResult and getDvResult are Export API helpers. They require a secret key and an organization with the Export API product enabled. getDvResult returns decoded document fields and images from the protobuf stream. See Export API.
5. AML Screening
screenAml runs a live AML screening for a person or a company, outside any verification flow. It queries the configured screening provider and returns its raw matches: nothing is scored or stored.
import { screenAml, type SDKOptions } from '@zyphe-sdk/node'
const opts: SDKOptions = {
apiKey: '<your-secret-api-key>',
environment: 'production',
}
const { error, data } = await screenAml(
{
organizationId: '<organization-id>',
isSandbox: true,
subjectType: 'PERSON', // or 'COMPANY'
query: {
name: 'Jane Doe',
birthDate: '1980-01-01',
country: 'nl',
},
},
opts,
)
if (error) {
console.error(error)
} else {
for (const entity of data.entities) {
console.log(entity.name, entity.score)
}
}
queryaccepts the person or company attributes you have, such asname,firstName,lastName,birthDate,country,nationality,registrationNumber, orpassportNumber. The more you send, the fewer false positives.- The response lists the raw provider matches in
entities(each with a matchscore), with theproviderand entityformatused. - AML screening is billed per call. For how screening works inside a flow, see AML.
6. KYB Cases
Every KYB result is handled as a case: its status, due-diligence level, rating, officers, and decision. The case helpers address it by caseId, the ID of the KYB case (the same value as the KYB result ID).
All KYB helpers require a secret key.
Read a Case
import { getKybCase, getKybCaseOfficers, type SDKOptions } from '@zyphe-sdk/node'
const opts: SDKOptions = {
apiKey: '<your-secret-api-key>',
environment: 'production',
}
const params = { organizationId: '<organization-id>', caseId: '<case-id>', isSandbox: true }
const { data: kybCase } = await getKybCase(params, opts)
console.log(kybCase?.status, kybCase?.dueDiligenceLevel, kybCase?.rating)
const { data: officers } = await getKybCaseOfficers(params, opts)
console.log(officers?.ubos.length, 'UBOs,', officers?.directors.length, 'directors')
getKybCasereturns the case as the dashboard reads it:status(OPEN,PROCESSING,WAITING_INPUT,ACTION_REQUIRED,READY_FOR_REVIEW,APPROVED,REJECTED,CANCELLED),dueDiligenceLevel,rating, assignment, what the case is waiting on, and each attempt at deciding it.getKybCaseOfficersreturns the case'subosanddirectors. Each person carries the stableidthe officer helpers below use.
Download the Case Report
import { writeFile } from 'node:fs/promises'
import { getKybCaseReport } from '@zyphe-sdk/node'
const { data: pdf, etag, fileName, error } = await getKybCaseReport(params, opts)
if (pdf) await writeFile(fileName ?? 'kyb_report.pdf', pdf)
// Later: download it again only if something in the report has changed
const { notModified } = await getKybCaseReport({ ...params, ifNoneMatch: etag }, opts)
getKybCaseReportreturns the case's KYB report as a PDF (datais aUint8Array): company and register data, directors, ownership and beneficial owners, screening, adverse media, documents and data sources, as the case stands when you call it. Every report has the same sections; a section the case has no data for says so.- The report is identified by a hash of its content, returned as
etag. Pass it back asifNoneMatch: when nothing in the report has changed the call answersnotModified: truewith no body. - Each download is recorded on the organization's audit log.
Manage UBOs and Directors
import { addKybCaseUbos, removeKybCaseDirector, sendKybCaseUboReminder } from '@zyphe-sdk/node'
const { data: officers } = await addKybCaseUbos(
{
...params,
persons: [
{
firstName: 'Jane',
lastName: 'Doe',
dateOfBirth: '1980-01-01',
country: 'nl',
gender: 'F',
// With an email the person is invited to complete full KYC;
// without one they are screened by name only (AML)
email: 'jane.doe@example.com',
},
],
},
opts,
)
// Invite the UBO just added to complete their KYC
await sendKybCaseUboReminder({ ...params, recipientIndex: officers!.ubos.length - 1 }, opts)
await removeKybCaseDirector({ ...params, personId: '<director-id>' }, opts)
addKybCaseUbos/addKybCaseDirectorsadd one or more people;removeKybCaseUbo/removeKybCaseDirectorremove one bypersonId. All four answer with the case's officers as they stand after the change ({ caseId, ubos, directors }).sendKybCaseUboRemindersends (or re-sends) the KYC invitation to the UBO atrecipientIndexin theuboslist. Only a UBO with an email can be invited.
Request a Document
import { requestKybCaseOnDemandDocument } from '@zyphe-sdk/node'
const { data: documentRequest } = await requestKybCaseOnDemandDocument(
{
...params,
documentKey: '<on-demand-document-key>',
recipientEmail: 'compliance@company.com', // optional
subject: 'One more document', // optional
body: 'Please upload the requested document.', // optional
},
opts,
)
documentKeymust name a document the flow marks as on demand. A document the flow requires upfront is rejected.
Decide a Case
import { decideKybCase } from '@zyphe-sdk/node'
const { data: decision, response } = await decideKybCase(
{
...params,
outcome: 'APPROVED', // or 'REJECTED'
reason: 'Checks complete', // mandatory on a rejection
dueDiligenceLevel: 'standard', // optional, approvals only
},
opts,
)
if (response.status === 202) {
// The organization's approval matrix asks for a second signature
console.log('Awaiting:', decision?.decisionProposal)
} else {
console.log('Decided:', decision?.outcome, decision?.statusAfter)
}
- A decision is final: a decided case cannot be decided again.
- An approval needs the case in
READY_FOR_REVIEW. A rejection is also accepted fromACTION_REQUIRED, and always needs areason. Add areasonCode(for exampleSANCTIONS_CONFIRMEDorOUTSIDE_RISK_APPETITE) to classify it. - When the organization's approval matrix requires two signatures, the call answers
202with adecisionProposallisting the roles it awaits. An API key can take the first signature only: the case closes when a person holding one of those roles confirms the proposal in the dashboard. SendingconfirmingProposalIdwith an API key is refused withkyb_case_second_signature_required.
Deprecated KYB Helpers
The helpers below address the case by kybResultId and call endpoints that are deprecated. They keep working, but new integrations should use the case helpers:
| Deprecated | Use instead |
|---|---|
addKybUbos | addKybCaseUbos |
removeKybUbo | removeKybCaseUbo |
addKybDirectors | addKybCaseDirectors |
removeKybDirector | removeKybCaseDirector |
requestKybOnDemandDocument | requestKybCaseOnDemandDocument |
sendKybUboReminder | sendKybCaseUboReminder |
When migrating, pass the same ID as caseId. The officer helpers now answer with { caseId, ubos, directors } instead of the whole KYB result, so read ubos / directors instead of uboDefinitions / directorDefinitions. The migration guide walks through every change.
7. Transaction Monitoring
Stream payment events from your backend and get the synchronous Allow / Review / Block verdict. See Transaction Monitoring for how rules, lists, and thresholds produce it.
import { getTransaction, ingestTransaction, updateTransactionStatus, type SDKOptions } from '@zyphe-sdk/node'
const opts: SDKOptions = {
apiKey: '<your-secret-api-key>',
environment: 'production',
}
const { error, data: verdict } = await ingestTransaction(
{
organizationId: '<organization-id>',
isSandbox: true,
transaction: {
externalId: 'txn_123', // your ID, also the idempotency key
identityId: '<verified-identity-id>',
direction: 'WITHDRAWAL', // 'DEPOSIT' | 'WITHDRAWAL' | 'TRANSFER'
amountMinor: 125000, // integer minor units: 1,250.00 EUR
currency: 'EUR',
occurredAt: new Date().toISOString(),
channel: 'sepa', // optional
description: 'Invoice 2026-042', // optional, screened for keywords
counterparty: { kind: 'PERSON', name: 'John Smith', address: 'NL91ABNA0417164300' }, // optional
},
},
opts,
)
if (error) {
console.error(error)
} else {
console.log(verdict.decision, verdict.riskProbability, verdict.firedRules, verdict.alerts)
}
// Read it back, then report the payment lifecycle
const { data: transaction } = await getTransaction(
{ organizationId: '<organization-id>', transactionId: verdict!.transactionId, isSandbox: true },
opts,
)
await updateTransactionStatus(
{ organizationId: '<organization-id>', transactionId: verdict!.transactionId, status: 'SETTLED', isSandbox: true },
opts,
)
ingestTransactionis idempotent onexternalId: sending the same event again returns the original verdict withreplay: true, without re-evaluating or billing it twice. Retrying with the sameexternalIdis the safe recovery after a timeout or a5xx.amountMinoris an integer in minor units (cents); there is no float money.updateTransactionStatussets the payment status:PENDING,SETTLED,FAILED,CANCELED, orREVERSED. Moving a transaction toFAILED,CANCELED, orREVERSEDremoves it from the velocity counters once; retrying the same update is safe.- Transaction ingestion is billed per ingested transaction (replays are not billed).
8. Types and Configuration
InitializeZypheFlowParams,SDKOptions,SdkCreateVerificationRequestResponse, for type-safe integration.Environments,EnvironmentConfigs,API_KEY_HEADER, for environment and API configuration.