Quickstart
The identity verification quickstart.
Sandbox key to first signed webhook, in order. Each step has a working example — copy it, swap in your own key and URLs, and run it.
Step by step
Seven steps, one afternoon.
Get a sandbox key
Sandbox keys (prefix vk_sand_) are issued on request while self-serve signup is being built. Ask through the contact form — most requests turn around the same business day. Sandbox sessions are never billed, so there's no reason to wait to start.
Create your first session
One call. Send a return_url (where the applicant lands when they finish) and a webhook_url (where the signed result goes). You get back a session_id and a verification_url.
bashcurl -X POST https://api.shieldid.ca/v1/sessions \ -H "X-Api-Key: vk_sand_your_key" \ -H "Content-Type: application/json" \ -d '{ "return_url": "https://yourapp.example/verified", "webhook_url": "https://yourapp.example/hooks/shieldid", "expected_name": "Jordan Smith", "locale": "en", "metadata": { "application_id": "app_9182" } }'Hand the applicant the link
Open verification_url and you'll see the hosted page. On a desktop browser it shows a QR code — scanning it hands the identical link to a phone, which is where the actual document photo, liveness challenge and selfie happen. Try it on your own phone right now.
Stand up a webhook endpoint
Point webhook_url at a route that accepts a POST, reads the raw request body (you need it unparsed to check the signature), and returns a 2xx status. Anything else and ShieldID retries the delivery up to three times.
javascriptimport express from "express"; const app = express(); app.post("/hooks/shieldid", express.raw({ type: "application/json" }), (req, res) => { // Verify the signature before you trust this body — see the next step. console.log("Received a webhook:", req.body.toString()); res.status(200).send("ok"); }); app.listen(3000);Verify the signature
Don't act on a webhook body until you've checked it's genuinely from ShieldID. Compute an HMAC-SHA256 over the raw body with your webhook secret and compare it to the X-Bio-Signature header.
javascriptimport crypto from "node:crypto"; function isValidShieldIdSignature(rawBody, signatureHeader, secret) { const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex"); const provided = signatureHeader || ""; const a = Buffer.from(expected, "hex"); const b = Buffer.from(provided, "hex"); return a.length === b.length && crypto.timingSafeEqual(a, b); } // rawBody must be the exact, unparsed request body app.post("/hooks/shieldid", express.raw({ type: "application/json" }), (req, res) => { const ok = isValidShieldIdSignature(req.body, req.headers["x-bio-signature"], process.env.SHIELDID_WEBHOOK_SECRET); if (!ok) return res.status(401).send("invalid signature"); const result = JSON.parse(req.body); // result.session_id, result.status, result.checks, … res.status(200).send("ok"); });Read the result
The webhook payload already has everything you need. If you'd rather pull the result yourself — after a redirect, say — call the result endpoint directly.
bashcurl https://api.shieldid.ca/v1/sessions/ses_9f2c1a/result \ -H "X-Api-Key: vk_sand_your_key" # → { "session_id": "ses_9f2c1a", "status": "VERIFIED", "checks": { "document": "PASS", … }, … }Handle FAILED and a retry
A session that lands on FAILED carries a failure_reason. Show the applicant something useful. Coming soon: retrying a FAILED session to reset it to PENDING. Today, treat FAILED as terminal and create a new session if the applicant wants to try again.
Before you flip the switch
Go to production.
The request shape never changes between sandbox and production — only the key. Confirm these before you send a real applicant through a vk_live_ session.
- Swap vk_sand_ for a vk_live_ key. Nothing else about the request changes.
- Point webhook_url at a real, always-on endpoint that verifies the signature — not a request-bin or a laptop.
- Handle all three terminal states in your app: VERIFIED, FAILED and EXPIRED, not just the happy path.
- Confirm your return_url reads the session_id and status query parameters ShieldID appends on redirect.
- Check current pricing with GET /v1/pricing/{country} or the pricing page, so production charges aren't a surprise.
- Keep sandbox keys out of anything a real applicant could reach — a sandbox session is free because it's not real.
Questions
Quickstart FAQ.
Get started
Run the first step right now.
Request a sandbox key, paste the first cURL command into a terminal, and you're already ahead of most integrations.