Scanify Docs

Guides

Identity verification (Swiss ID card & passport)

A guided flow that proves a real, present person holds a valid Swiss identity document: explicit consent, live document capture, and a short face video with unpredictable liveness challenges. The widget only captures and uploads — every score and the final decision are computed on the server, never on the visitor's device.

This is not the same as data capture. The data-capture flow reads fields off a card and hands them to your page. This flow runs a full identity-proofing pipeline (MRZ re-validation, face match, liveness, a combined decision, an archived evidence chain) modelled on ETSI TS 119 461, ISO/IEC 30107 and ICAO 9303, and results land in your dashboard's Verifizierungen queue.

Quick start

<button type="button" data-jsscanner-verify-trigger
        data-verify-type="swiss_passport">
  Identität verifizieren
</button>
<script src="https://your-app.test/embed/YOUR_TOKEN.js"></script>

Use data-verify-type="swiss_id_card" for the identity card instead.

The flow

  1. Consent. The exact statement shown is stored verbatim plus a SHA-256 hash, with a policy version, timestamp and IP (nDSG). Nothing starts before it is accepted.
  2. Document capture. Live camera only. The ID card is captured front then back; the passport is a single data page. A blur check offers a retake before anything is sent. The MRZ side is OCR'd in the browser as a hint — the server re-reads and re-checks every check digit itself and never trusts the client's parse.
  3. Face video. The front camera records while the visitor performs a server-decided sequence of challenges (turn head, blink) and reads a random 4-digit code aloud. The order and the code are unpredictable to the client, which is what defeats a pre-rendered deepfake, and the spoken code binds the recording to this specific session.
  4. Server processing. MRZ validation, document-face ↔ live-face match, liveness/PAD analysis and speech verification, then a three-way decision: approved, rejected, or manual review for anything in between.
  5. Result. The widget polls and shows the outcome. Grey-area cases go to the review queue in your dashboard, where a trained reviewer decides with the full evidence in front of them.

Customization

AttributeEffect
data-verify-typeRequired. swiss_id_card or swiss_passport.
data-verify-accentHighlight color for guide frames and buttons.
data-verify-referenceYour own id (e.g. a signature-session id) stored on the verification for later correlation.
data-jsscanner-onverifyName of a global function called once with { uuid, status, decision } when the flow ends.

Getting the result

document.addEventListener("jsscanner:verify", (event) => {
  const { uuid, status, decision } = event.detail;
  // status: "approved" | "rejected" | "awaiting_review" | "retry_requested"
  // decision: "approved" | "rejected" | "manual_review" | null
});

Or name a callback:

<button type="button" data-jsscanner-verify-trigger
        data-verify-type="swiss_id_card"
        data-jsscanner-onverify="onVerified">
  Verifizieren
</button>

<script>
  function onVerified(result) {
    if (result.status === "approved") { /* continue your flow */ }
  }
</script>

Good to know

  • The final status the widget reports is not authoritative on its own — a grey-area result is awaiting_review until a person decides. Treat approved as the only "pass".
  • Document images, the video and representative frames are stored encrypted, with a plaintext SHA-256 per file, and deleted after the retention window — the decision, scores and audit trail are kept as a tombstone.
  • Each run counts against the project's monthly verification limit (separate from the scan and session limits).
  • The in-browser MRZ read uses Tesseract.js loaded from a pinned CDN version on first use. It is only a hint; the server's own validation is what counts.