Run a verification
🔑 Auth: SDK client (App API key) + a
workflowId· 👤 Include the connected user’svalyd_access_tokenso the proof saves to their Valyd ID
Start a verification session for your workflow, send the user to Valyd’s verification page — Valyd handles the capture UI, camera, retries, and security — and read one combined decision when they’re done. There is no capture UI to build.
The flow at a glance:
- Create a session on your server with a
workflowId(and the user’svalyd_access_token). - Redirect the user’s browser to the returned
url. - Valyd captures everything and redirects back to your
redirectUrl. - Receive a signed webhook, then fetch the authoritative result with
verify.sessions.decision(id).
Prerequisites
All from Create a workflow — developer setup, not part of your end-user flow:
- The App API key, copied at app creation (shown once). Store it server-side only.
- A
workflowIdfrom a workflow you created in the Developer Portal . - A webhook URL + signing secret configured under Webhooks in the portal.
- The connected user’s
valyd_access_tokenfrom Connect with Valyd, so the passed proofs save to their Valyd ID and already-proven steps are skipped.
Install and initialize the SDK:
npm i @valyd/sdkimport { VerifyClient } from "@valyd/sdk";
const verify = new VerifyClient({
apiKey: process.env.VALYD_API_KEY,
webhookSecret: process.env.VALYD_WEBHOOK_SECRET, // used by verify.webhooks.constructEvent
});Create a session
Call verify.sessions.create from your backend. The response includes the url you send the
user to:
const session = await verify.sessions.create({
workflowId: process.env.VALYD_WORKFLOW_ID, // the checks you picked in the portal
valydAccessToken: accessToken, // ties the run to the connected user
redirectUrl: "https://app.example.com/verify/callback",
callback: "https://api.example.com/webhooks/valyd",
vendorData: "user_123", // your internal ref — echoed back on the webhook
ttlSeconds: 900,
});
// session.url → redirect the user's browser here
// session.sessionId, session.sessionToken, session.expiresAtThe returned session carries the verification-page url plus sessionId, sessionToken,
features, redirectUrl, and expiresAt:
{
"sessionId": "ses_…",
"status": "NOT_STARTED",
"url": "https://idp.valyd.work/s/…",
"sessionToken": "stk_…",
"features": ["id_verification","liveness","face_match","credential"],
"redirectUrl": "https://app.example.com/verify/callback",
"expiresAt": "2026-06-11T12:00:00Z"
}Keep your API key server-side only — never create a session from the browser. Only the session
urland thesessionTokenare safe to send to the client.VALYD_WORKFLOW_IDis theworkflowIdfrom the Developer Portal (https://dev.valyd.work → Workflows).
Then redirect the user’s browser to session.url (e.g. res.redirect(session.url)). Valyd renders
the whole capture and verification UI; the steps auto-adapt to the workflow’s checks. With the
user’s token on the session, the run pre-fills from their account, skips already-proven steps, and
saves passed proofs to their Valyd ID.
After the user returns
Valyd sends the user’s browser back to your redirectUrl with ?session_id=…&status=…. Treat
status as a hint only — never grant access on that query param. Fetch the authoritative outcome
with verify.sessions.decision(id) (or wait for the signed webhook):
const decision = await verify.sessions.decision(sessionId);
// decision.status, decision.checks[]- Session lifecycle — the full state machine and what to do at each stage.
- Results & decisions — every status value, the per-check statuses, and reading the decision payload.
- Webhooks — the signed terminal-state callback and how to verify it.
Other session helpers
const session = await verify.sessions.retrieve(sessionId);
const page = await verify.sessions.list({ status: "APPROVED", vendorData: "user_123", limit: 50 });
await verify.sessions.updateStatus(sessionId, "APPROVED"); // or "DECLINED" — manual review decision (IN_REVIEW sessions only)SDK surface used in this flow
| SDK method | Purpose |
|---|---|
verify.sessions.create({ workflowId, valydAccessToken?, redirectUrl, callback?, vendorData?, ttlSeconds? }) | Create a verification session. |
verify.sessions.decision(id) | Read the authoritative decision and per-check breakdown. |
verify.sessions.retrieve(id) | Retrieve a session. |
verify.sessions.list({ status?, vendorData?, limit? }) | List sessions. |
verify.sessions.updateStatus(id, "APPROVED" | "DECLINED") | Manually decide an IN_REVIEW session (approval still requires passed ID, liveness, and face-match checks). |
verify.webhooks.constructEvent(rawBody, headers) | Verify and parse the signed webhook Valyd POSTs to your callback. |
Workflow CRUD is not in the SDK — compose workflows in the Developer Portal and pass the resulting
workflowIdtoverify.sessions.create({ workflowId, … }).
Full implementation example
The complete Express integration — create the session, handle the redirect back (status is a hint only), verify the signed webhook, then pull the authoritative decision:
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,
});
// 1) Start verification
app.post("/start-verification", express.json(), async (req, res) => {
const session = await verify.sessions.create({
workflowId: process.env.VALYD_WORKFLOW_ID, // license-only OR kyc+license
redirectUrl: `${process.env.APP_URL}/verify/callback`,
callback: `${process.env.APP_URL}/webhooks/valyd`,
vendorData: req.body.userId,
});
res.json({ url: session.url, sessionId: session.sessionId });
});
// 2) Redirect-back (status is a hint only)
app.get("/verify/callback", (req, res) => {
res.redirect(`/verify/pending?s=${req.query.session_id}`);
});
// 3) Signed webhook — MUST use raw body
app.post(
"/webhooks/valyd",
express.raw({ type: "application/json" }),
async (req, res) => {
try {
const event = verify.webhooks.constructEvent(req.body, req.headers);
// 4) Pull the full decision (webhook is a notification)
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;
}
}
);
app.listen(3000);Common errors
API_KEY_INVALID— missing, wrong, or rotated API key, or it was used client-side. Re-copy the key from the portal (or rotate it), setVALYD_API_KEYserver-side, and pass it tonew VerifyClient({ apiKey }). Never expose it in browser code.VALIDATION_ERRORonverify.sessions.create— missing or invalidworkflowId. Create a workflow in the portal, setVALYD_WORKFLOW_ID, and verifyecho $VALYD_WORKFLOW_IDis non-empty.- No webhook received — webhook URL/signing secret not configured, or your endpoint is not
publicly reachable. Set them in Portal → Webhooks, ensure the URL is publicly reachable, and
verify the signature via
verify.webhooks.constructEventbefore trusting the event.
Next steps
- Compose the checks — Workflows · Checks reference.
- Track the run — Session lifecycle.
- Read the result — Results & decisions and Webhooks.