Node SDK
Recipe
Prerequisites
- Node 18+ installed. Check with:
Expected output:
node --versionv18.x.xor higher. If lower, upgrade Node before continuing. - Credentials from the Valyd Developer Portal (https://dev.valyd.work ):
VALYD_API_KEY— the App API key the SDK client authenticates with on every request.VALYD_WEBHOOK_SECRET— needed to verify webhook signatures (Reusable Verification).VALYD_WORKFLOW_ID— needed when creating verification sessions.
IF you are building Reusable Verification (send the user to Valyd's verification page):
→ you need VALYD_API_KEY, VALYD_WEBHOOK_SECRET, and VALYD_WORKFLOW_ID
IF you are using the Unique Human API (a no-account session for a liveness/uniqueness workflow):
→ you need VALYD_API_KEY and VALYD_WORKFLOW_ID
IF unsure which credentials you have:
→ log in to https://dev.valyd.work and check your app's API keys / webhooks / workflowsSteps
-
Install the SDK.
npm i @valyd/sdkExpected output: npm adds
@valyd/sdkat its latest published version todependenciesinpackage.json, so a fresh install always pulls the newest release. -
Set environment variables (e.g. in a
.envfile or your process environment). Get each value from the Valyd Developer Portal: https://dev.valyd.work .VALYD_API_KEY=your_api_key_here # App API key for every request VALYD_WEBHOOK_SECRET=your_webhook_secret # required for webhook handling VALYD_WORKFLOW_ID=your_workflow_id # required to create verification sessionsExpected output: no output; these are read at runtime via
process.env.*. -
Initialise the client in your server code.
import { VerifyClient } from "@valyd/sdk"; const verify = new VerifyClient({ apiKey: process.env.VALYD_API_KEY!, });Expected output: a
VerifyClientinstance. No network call is made on construction. IfapiKeyis missing, a later call throwsValydVerifyErrorwith codeconfig_error.
Constructor options
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | — | Required. The App API key the client authenticates with on every request. |
baseUrl | string | https://idp.valyd.work | API base URL. Override only for staging/self-hosted. |
webhookSecret | string | — | Optional. When set, webhooks.constructEvent / verify can be called without passing the secret explicitly. |
timeoutMs | number | 15000 | Per-request timeout. Credential lookups (credentialVerification, kycCredential) automatically use at least 60s — set a higher value here only if you want a bigger floor for all calls. |
fetch | typeof fetch | — | Custom fetch implementation (proxies, instrumentation, tests). |
Authentication
Every Verification API call is authenticated by your App API key — the apiKey you pass to the
constructor. This is the credential that matters for the SDK; get it from the Developer Portal → your
project → Credentials.
apiKey(vrf_…) — authenticates allverify.*calls (sessions, the Unique Human API, workflows). This is the only credential the SDK needs to make requests.webhookSecret(whsec_…) — NOT an auth credential for outbound calls. It is used only to verify the HMAC signature on incoming webhooks (verify.webhooks.constructEvent).client_id/client_secret— these belong to Connect with Valyd (standard OAuth 2.0 / OIDC), the sign-in step of Reusable Verification. They do not authenticate verification calls; use the App API key for that. (You hold both credentials, used independently.)
So: integrations that only call the Verification API need just the apiKey. There is no constructor form that
authenticates verify calls without it.
Resources
After initialising verify, use these resource namespaces.
verify.sessions
create(params): Promise<Session>— Create a verification session. Returns.urland.sessionId— see Run a verification.retrieve(id): Promise<Session>— Fetch a session by id.list({ status?, vendorData?, limit? }): Promise<SessionSummary[]>— List sessions, filterable by status / vendor_data.decision(id): Promise<Decision>— Authoritative result with.checks[]— call this after the webhook.updateStatus(id, "APPROVED" | "DECLINED"): Promise<Session>— Manual override (e.g. after agent review).
Workflows are composed in the Developer Portal — the Node SDK does not expose workflow CRUD. You pass the resulting
workflowIdtosessions.create(...).
The Unique Human API
The Unique Human API is the same verify.sessions surface with no user token: create a
session for a workflow containing the anti-spoof and/or face-uniqueness checks, redirect the
person to session.url, and read the verdict from verify.sessions.decision(). No account is
involved, the result returns to you, and nothing is saved to one. See the
Unique Human API reference.
faceUniquenessUnlink(valydUuid)— GDPR: forget this project’s link to a face id (deletes the face entirely when no remaining project or Valyd account knows it).
ID/KYC, face match, age, professional license, and location run only as workflow checks in Reusable Verification (a verification session for a user who connected with Valyd), never as their own public APIs. The SDK’s other low-level
verify.standalone.*methods remain for compatibility and are not part of the public products.
Every billable check also accepts an optional idempotencyKey — sent as the
Idempotency-Key header so a network retry can never double-charge or double-run.
verify.credentials
states(): Promise<CredentialState[]>— List supported states.providers(state): Promise<CredentialProvider[]>— List providers (license types) in a state, withrequired_fields.
verify.webhooks
constructEvent(rawBody, headers, secret?, { toleranceSeconds? }): WebhookEvent— Verifies the HMAC signature and returns the parsed event. ThrowsValydVerifyErrorwith codeinvalid_signatureon mismatch.verify(rawBody, headers, secret?, { toleranceSeconds? }): boolean— Boolean check, no parse, no throw.
Also exported as top-level constructEvent / verify. When webhookSecret is set on the client, the secret arg is optional.
Helpers & types
readImage and ImageInput cover all the ways an image can be supplied:
import { readImage, type ImageInput } from "@valyd/sdk";
// ImageInput accepted everywhere an image is required:
// Buffer | Uint8Array | base64 string | data-URL string
const fromFile: ImageInput = readImage("./id_front.jpg"); // reads to base64
const fromBuf: ImageInput = await fs.promises.readFile("./selfie.jpg");
const fromDataUrl: ImageInput = "data:image/jpeg;base64,/9j/4AAQ...";Every response is strongly typed. Public API uses camelCase; wire payloads stay snake_case.
import type {
Session,
SessionSummary,
Decision,
Check,
CheckEnvelope,
KycCredentialResult,
Workflow,
CredentialState,
CredentialProvider,
WebhookEvent,
} from "@valyd/sdk";Error handling
Every failure throws ValydVerifyError with { code, status?, data? }. The code is either an API code (e.g. API_KEY_INVALID, VALIDATION_ERROR) or an SDK code:
network_error— DNS/socket failure.timeout— exceededtimeoutMs.invalid_signature— webhook HMAC mismatch or stale timestamp.config_error— missingapiKey/webhookSecret.
import { VerifyClient, ValydVerifyError } from "@valyd/sdk";
const verify = new VerifyClient({ apiKey: process.env.VALYD_API_KEY! });
try {
const session = await verify.sessions.create({
workflowId: process.env.VALYD_WORKFLOW_ID!,
redirectUrl: "https://yourapp.com/checked",
});
} catch (err) {
if (err instanceof ValydVerifyError) {
console.error(err.code, err.status, err.message, err.data);
if (err.code === "API_KEY_INVALID") { /* rotate / refetch */ }
} else {
throw err;
}
}Quickstarts
Reusable Verification quickstart
import { VerifyClient } from "@valyd/sdk";
const verify = new VerifyClient({
apiKey: process.env.VALYD_API_KEY!,
webhookSecret: process.env.VALYD_WEBHOOK_SECRET!,
});
// 1) Create a session and redirect the user
const session = await verify.sessions.create({
workflowId: process.env.VALYD_WORKFLOW_ID!,
redirectUrl: "https://app.example.com/verify/callback",
callback: "https://api.example.com/webhooks/valyd",
vendorData: "user_123",
});
// res.redirect(session.url)
// 2) In your webhook handler:
const event = verify.webhooks.constructEvent(rawBody, headers); // throws on bad signature
// 3) Pull the authoritative decision
const decision = await verify.sessions.decision(event.sessionId);
// decision.status, decision.checks[]Expected output: verify.sessions.create(...) resolves to a Session with .url (redirect the user here) and .sessionId. After the user finishes, your webhook fires; constructEvent returns the parsed WebhookEvent, and verify.sessions.decision(...) resolves to a Decision with .status and .checks[].
Unique Human API quickstart
The Unique Human API is API-key-only — a no-account session for a workflow containing the liveness and/or uniqueness checks; the person is redirected to Valyd’s verification page and nothing is saved to an account. (ID/KYC, face match, age, license, and location run as workflow checks in Reusable Verification instead.)
import { VerifyClient } from "@valyd/sdk";
const verify = new VerifyClient({ apiKey: process.env.VALYD_API_KEY });
const session = await verify.sessions.create({
workflowId: process.env.VALYD_WORKFLOW_ID, // liveness and/or uniqueness
redirectUrl: "https://yourapp.com/checked",
});
// → redirect the person to session.url, then:
const decision = await verify.sessions.decision(session.sessionId);
// antispoof check data → { human_score, assurance: "captured", ... }
// face_uniqueness check data → { valyd_uuid, registered: "new" | "existing" }Expected output: the decision’s status is "APPROVED" on a live, unique capture, with the per-check data on decision.checks[].
Express webhook
Use express.raw() so the body bytes match what Valyd signed.
import express from "express";
import { VerifyClient, ValydVerifyError } from "@valyd/sdk";
const app = express();
const verify = new VerifyClient({
apiKey: process.env.VALYD_API_KEY!,
webhookSecret: process.env.VALYD_WEBHOOK_SECRET!,
});
app.post(
"/webhooks/valyd",
express.raw({ type: "application/json" }),
async (req, res) => {
try {
const event = verify.webhooks.constructEvent(req.body, req.headers);
const decision = await verify.sessions.decision(event.sessionId);
await persist(event.vendorData, decision);
res.json({ ok: true });
} catch (err) {
if (err instanceof ValydVerifyError && err.code === "invalid_signature") {
return res.status(400).send("bad signature");
}
throw err;
}
}
);Expected output: on a valid signature the handler responds 200 with { "ok": true }; on a bad signature it responds 400 with body bad signature.
Verification
- Confirm the SDK is installed:
Expected output:
npm ls @valyd/sdk@valyd/sdkat its latest published version. - Confirm credentials are wired (only needs
VALYD_API_KEY):Expected output: a number greater than 0. If it throwsimport { VerifyClient } from "@valyd/sdk"; const verify = new VerifyClient({ apiKey: process.env.VALYD_API_KEY! }); const { states } = await verify.credentials.states(); console.log(states.length); // > 0 means the API key worksValydVerifyErrorwith codeAPI_KEY_INVALID, the key is wrong or missing.
Common errors
-
ValydVerifyErrorcodeconfig_error- Cause:
apiKey(orwebhookSecretfor webhook calls) was not provided toVerifyClient. - Fix: Set
VALYD_API_KEY(andVALYD_WEBHOOK_SECRETfor webhooks) in the environment and pass them to the constructor:new VerifyClient({ apiKey: process.env.VALYD_API_KEY!, webhookSecret: process.env.VALYD_WEBHOOK_SECRET! }).
- Cause:
-
ValydVerifyErrorcodeinvalid_signaturein the webhook handler- Cause: webhook HMAC mismatch or stale timestamp — most often because the request body was parsed/re-serialized before signature verification, so the bytes no longer match what Valyd signed.
- Fix: Mount the webhook route with
express.raw({ type: "application/json" })soreq.bodyis the exact raw bytes, and make sure thewebhookSecretmatches the one in the dashboard.
-
ValydVerifyErrorcodetimeout- Cause: the request exceeded
timeoutMs(default15000). Credential lookups can be slow. - Fix: Increase the per-request timeout for credential lookups (10–60s), e.g.
new VerifyClient({ apiKey: process.env.VALYD_API_KEY!, timeoutMs: 90_000 }).
- Cause: the request exceeded