Skip to main content
ShieldID

API reference

The identity verification API reference.

Every endpoint you can call, every field you'll see back, and what to do with a webhook once it arrives. For exact field types, treat the live OpenAPI document at /docs on your own API origin as the source of truth — this page is the plain-language version.

Base URL and environments

One base URL. Two environments.

Every request goes to the same host. Which environment you're in is decided entirely by which API key you send.

Every request in this reference assumes these two things.
Base URL (this documentation)https://api.shieldid.ca — illustrative; your production API origin is published together with your API keys.
Auth headerX-Api-Key: <your key>, on every request except the two public endpoints noted below.
Sandbox key prefixvk_sand_… — sessions created with this key are stamped environment: "sandbox" and are never billed.
Production key prefixvk_live_… — the same requests, charged per your plan.
Content typeapplication/json on every request body and response.

Endpoints

The full v1 surface.

Six JSON endpoints, plus a hosted page and a public credential check that aren't JSON at all — you just link to them.

JSON endpoints under /v1.
EndpointAuthWhat it does
POST /v1/sessionsX-Api-KeyCreate a verification session. Requires a return_url and a webhook_url. Returns session_id, verification_url and status: "PENDING".
GET /v1/sessions/{id}X-Api-KeyStatus and top-line fields for one session: status, confidence, environment.
GET /v1/sessions/{id}/resultX-Api-KeyThe full result once a session reaches VERIFIED or FAILED: person, per-check verdicts, confidence, verified_until.
DELETE /v1/sessions/{id}X-Api-KeyCancel a session that is still PENDING or IN_PROGRESS. A session already at VERIFIED, FAILED or EXPIRED can't be canceled.
GET /v1/credentials/{token}None — publicCheck whether a verified credential is currently valid. No API key required, so it's callable from a browser. Note: today's response also includes the verified person's name and document type/country, not just a valid/invalid flag — do not hand this link to a third party you don't want to see that.
GET /v1/pricing/{country}X-Api-KeyPer-country pricing for a two-letter ISO 3166-1 country code.
Pages you link to, not JSON you parse.
/v/{session_id}The hosted verification page — the verification_url a session returns. Desktop shows a QR code; scanning it hands the applicant's phone the same link to capture their document and selfie.
/c/{session_id}A public, unauthenticated credential-check page for one verified session. Note: today's response also includes the verified person's name and document type/country, not just a valid/invalid flag — do not hand this link to a third party you don't want to see that.
/docsThe live, interactive OpenAPI reference for your account's API origin.

Creating a session

POST /v1/sessions, end to end.

The request, and exactly what comes back.

bash
curl -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" }
  }'

POST /v1/sessions — request fields.
return_urlstring, required — Where the applicant is sent back after they finish. Must be http or https.
webhook_urlstring, required — Where the signed result is delivered. See Webhooks below.
expected_namestring, required — The name you expect on the document, used to cross-check what the ID actually says.
expected_dob / expected_addressstring, optional — Cross-checked against the document when supplied; not required to run a verification.
localestring, optional — "en" or "fr". Sets the language of the hosted page the applicant sees.
metadataobject, optional — Your own reference data — an application id, a tenant id, anything you want echoed back unchanged in the result and the webhook.

The result

GET /v1/sessions/{id}/result — response shape.

The same shape is delivered in the webhook payload, so you rarely need to poll this endpoint at all.

Top-level fields.
session_idThe id you created.
statusPENDING, IN_PROGRESS, VERIFIED, FAILED or EXPIRED.
environment"sandbox" or "production", matching the key that created the session.
personThe fields read from the document: name, date of birth, document type, document country and jurisdiction, address.
checksOne verdict per check — see below. Never one opaque score.
confidenceA single overall figure for the verification. Individual checks, including face match, are still reported on their own against a fixed decision line — this field doesn't replace them.
verified_untilHow long the result is considered current, on a VERIFIED session.
failure_reasonSet on a FAILED session — for example FACE_MATCH_BELOW_THRESHOLD or CANCELLED_BY_API. null otherwise.
metadataWhatever you sent when you created the session, unchanged.
json
{
  "session_id": "ses_9f2c1a",
  "status": "VERIFIED",
  "environment": "sandbox",
  "person": {
    "name": "Jordan Smith",
    "dob": "1994-03-11",
    "document_type": "drivers_licence",
    "document_country": "CA",
    "document_jurisdiction": "ON"
  },
  "checks": {
    "document": "PASS",
    "liveness": "PASS",
    "antispoof": "PASS",
    "face_match": "PASS"
  },
  "confidence": 88,
  "verified_until": "2027-03-11T00:00:00Z",
  "failure_reason": null,
  "metadata": { "application_id": "app_9182" }
}

Add-onchecks.document, checks.liveness, checks.antispoof and checks.face_match each resolve to PASS, FAIL or SKIPPED. Watchlist screening (checks.sanctions — CLEAR, MATCH or SKIPPED) is an add-on and only appears for accounts that turn it on.

Session lifecycle

Five states, one path.

A session moves forward on its own; you don't drive the state machine, you just read it.

status values, in order.
PENDINGCreated. Nobody has opened the verification_url yet.
IN_PROGRESSThe applicant opened the link and is somewhere in the capture flow.
VERIFIEDEvery check ran and the result is ready. Terminal — call the result endpoint or read the webhook.
FAILEDAt least one check didn't pass. failure_reason says which. Coming soon: a retry that resets the session to PENDING — until then, treat FAILED as terminal and create a new session if the applicant wants to try again.
EXPIREDNobody finished in time. Sessions expire automatically after a default window (30 minutes); create a new one to try again.

DELETE /v1/sessions/{id} cancels a PENDING or IN_PROGRESS session early. A session that already reached VERIFIED, FAILED or EXPIRED can't be canceled — it's already done.

Errors

What to plan for.

This is the shortlist worth coding against today. The full, current set of error codes and payloads is documented in the live OpenAPI at /docs.

HTTP status, error code and what it means.
StatusCodeMeaning
401UNAUTHORIZEDThe X-Api-Key header is missing.
401INVALID_KEY_FORMATThe key doesn't start with vk_sand_ or vk_live_, or doesn't match a key on the account.
403FORBIDDENThe key is valid but doesn't have access to the session or resource you asked for.
404NOT_FOUNDNo session, credential or resource exists with that id.
409CONFLICTThe session isn't in a state that allows the action — for example, canceling one that's already VERIFIED.
422VALIDATION_ERRORA required field is missing or malformed — for example, return_url isn't a valid http or https URL.

Webhooks

Signed, delivered, retried.

When a session reaches VERIFIED or FAILED, ShieldID POSTs the shape documented above to your webhook_url.

  • Delivered as a POST with a JSON body matching the result shape above.
  • Signed with HMAC-SHA256 over the raw request body, sent in an X-Bio-Signature header. Verify it against your webhook secret before you trust the payload.
  • Retried up to three times with a linear backoff if your endpoint doesn't answer with a 2xx status.
  • Coming soon: a FAILED-session retry that delivers a second webhook once it resolves. Today, treat one webhook per session as the rule.
  • Field names here match this reference. If what actually arrives looks different, trust the live payload over this page and tell us — the field list is kept in step with the API, not the other way around.

Verify the signature

javascript
import 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");
});

Idempotency and retries

Build handlers that are safe to run twice.

There's no dedicated idempotency key yet, on session creation or on webhook delivery.

  • A retried webhook delivery can arrive more than once for the same session. Key your handler on session_id + status and make writing the result idempotent.
  • Don't retry POST /v1/sessions blindly on a timeout — check whether the session was created first, or you may end up with two sessions for one applicant.
  • A dedicated Idempotency-Key header for session creation is planned.

Coming soonIdempotency-Key support on session creation

Keeping up

This reference changes. Watch the changelog.

Field additions, new error codes and endpoint changes are announced there before they land here.

Questions

Reference FAQ.

Next

Put this reference to work.

Follow the quickstart to go from a sandbox key to a verified session and a signed webhook, in order.