Prove there’s a real, live person
Anti-spoof · Liveness
A ready-to-go demo: let a user log in with Valyd and share their verified details, then run a passive-liveness check that flags presentation attacks — photos, masks, replays, deepfakes. Copy the credentials and the snippet below and you’re running in minutes.
Try the live anti-spoof check
Start a session, allow the camera in the tab that opens, and follow the prompts. In the hosted capture Valyd tracks your face, records a short live burst, and asks you to perform one random action (turn your head, open your mouth, nod, or move a little closer). It then decides whether it’s a real, live person — this active, capture-controlled path is what makes a photo, screen replay, or pre-recorded clip fail, because an attacker can’t know the action in advance.
How the check is graded. Every frame is scored for passive liveness and the results are combined into a single
human_score(0–100), together with motion analysis, same-face consistency across frames, and verification that the requested action actually happened. The hosted flow returns the strongest assurance level,captured. See the Anti-spoof API for the rawhuman_scorefields and the standalonePOST /api/v2/antispoof/POST /api/v2/antispoof/identityendpoints.
The demo runs entirely with the public app key (X-API-Key). The flow is:
POST /api/v2/sessionwith{ workflow_id, vendor_data }→ returns a hostedurl- Open the hosted url — the user does the live face capture there
- Poll
GET /api/v2/session/{session_id}/decision→ status + decision
The app key is safe in the browser: it can only start liveness sessions on the capped demo account. The client_secret is never used by the browser.
You can drive the same flow from your terminal:
# 1) Start a liveness-only session (public demo app key)
curl -X POST "https://idp.valyd.id/api/v2/session" \
-H "Content-Type: application/json" \
-H "X-API-Key: pvi34NsFwzlb7u7NDWNcIxpCxy3eCBrd5D-Pyzud3TU" \
-d '{ "workflow_id": "cd2e8501-28d0-45cb-aa4a-c11c998a1cf8", "vendor_data": "antispoof-demo-123" }'
# → { "data": { "session_id": "…", "url": "…", … } } — open `url` in a browser for the capture
# 2) Poll the decision until it reaches a terminal state
# (APPROVED | DECLINED | ABANDONED | EXPIRED end the poll; APPROVED = live human)
curl "https://idp.valyd.id/api/v2/session/SESSION_ID/decision" \
-H "X-API-Key: pvi34NsFwzlb7u7NDWNcIxpCxy3eCBrd5D-Pyzud3TU"- APPROVED → Live human confirmed — liveness passed.
- Any other terminal status → not verified (spoof suspected, abandoned, or expired).
Demo credentials
Public credentials on a capped demo account, so you can try everything immediately. In your own app, keep the client_secret and app key on your server. Create your own from the Valyd Dev Portal .
| Credential | Value |
|---|---|
OAuth client_id | d771fc3c206346c4b07c04997882cb53 |
OAuth client_secret (server-side only) | 0b3ac7e9f3c146709603f25f8f310c30 |
| Verify app key (server-side only) | pvi34NsFwzlb7u7NDWNcIxpCxy3eCBrd5D-Pyzud3TU |
Liveness workflow_id | cd2e8501-28d0-45cb-aa4a-c11c998a1cf8 |
These are public demo credentials on a dedicated, balance-capped Valyd account. They exist so anyone can run the anti-spoof demo end to end. The client_secret appears in the server-side snippet only — the in-browser demo authenticates with the public app key alone.
The flow
Login with Valyd
Redirect to Valyd, the user authorizes, you get a code and exchange it server-side.
Share details
Read the user’s shared profile + proofs (valyd_id, name, human_verified) from get-userinfo.
Verify face
Start the liveness workflow, open the hosted capture, poll the decision — human or not.
Server (Node) — login, share details, verify face
// server.js — Node 18+ (Express). Install: npm i express @valyd/sdk
// The client_secret and app key stay here on the server, never in the browser.
import express from "express";
import { ValydClient } from "@valyd/sdk";
const app = express();
const PORT = 3000;
const idp = new ValydClient({
clientId: "d771fc3c206346c4b07c04997882cb53",
clientSecret: "0b3ac7e9f3c146709603f25f8f310c30",
redirectUri: "http://localhost:3000/callback",
baseUrl: "https://idp.valyd.id",
});
const APP_KEY = "pvi34NsFwzlb7u7NDWNcIxpCxy3eCBrd5D-Pyzud3TU";
const WORKFLOW_ID = "cd2e8501-28d0-45cb-aa4a-c11c998a1cf8";
const IDP = "https://idp.valyd.id";
// 1) Login button → send the user to Valyd to authorize.
app.get("/login", (_req, res) => {
const url = idp.getAuthorizationUrl({
scope: ["profile","verifications"],
state: "demo-" + Date.now(),
});
res.redirect(url);
});
// 2) Callback → exchange the code, then read the user's shared details/proofs.
app.get("/callback", async (req, res) => {
const { accessToken } = await idp.exchangeCode(String(req.query.code));
const me = await idp.getUserInfo(accessToken); // { success, data: {...} }
const u = me.data;
res.json({
valyd_id: u.valyd_id,
name: u.full_name,
id_verified: u.id_verified,
human_verified: u.verifications?.human_verified,
});
});
// 3) Verify-face button → start a liveness-only session, return the hosted URL.
app.get("/verify-face", async (_req, res) => {
const r = await fetch(IDP + "/api/v2/session", {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": APP_KEY },
body: JSON.stringify({ workflow_id: WORKFLOW_ID, vendor_data: "demo-" + Date.now() }),
});
const { data } = await r.json();
// data = { session_id, status, url, session_token, features, expires_at }
res.json({ sessionId: data.session_id, open: data.url }); // open this URL for the capture
});
// 4) Poll the decision until it reaches a terminal state.
// status ∈ NOT_STARTED | IN_PROGRESS | IN_REVIEW | APPROVED | DECLINED | ABANDONED | EXPIRED
app.get("/result/:sessionId", async (req, res) => {
const r = await fetch(IDP + "/api/v2/session/" + req.params.sessionId + "/decision", {
headers: { "X-API-Key": APP_KEY },
});
const { data } = await r.json();
res.json({
status: data.status,
human: data.status === "APPROVED", // APPROVED → a real, live person
});
});
app.listen(PORT, () => console.log("Anti-spoof demo on http://localhost:" + PORT + "/login"));Browser — the two buttons
<!-- index.html — the two buttons your server backs -->
<button onclick="location.href='/login'">Login with Valyd</button>
<button onclick="verifyFace()">Verify your face</button>
<script>
async function verifyFace() {
const { open } = await (await fetch('/verify-face')).json();
window.open(open, '_blank'); // the hosted liveness capture
}
</script>Anti-spoof + identity (one call)
If you also want to know who the live person is, use POST /api/v2/antispoof/identity. It runs the same anti-spoof pipeline and, only when it passes, resolves the proven-live face against the global Valyd gallery — returning a stable valyd_ uuid. The same face always maps to the same uuid, so this is duplicate-account / sybil detection in a single request. See Anti-spoof + identity and Face uniqueness.