Guides
Data capture (Swiss ID card & credit card)
A guided, multi-step flow for structured document types: line up the card in a camera overlay, and get
back parsed data plus the photo(s) — instead of just a raw scan. Two flows ship today,
swiss_id and credit_card; both run entirely in the
browser, including OCR.
Separate from the barcode and document scanners. This is a third, independent module that
lazy-loads on first use, just like
document scanning — and
it lazy-loads its OCR engine (Tesseract.js) the same way, only once a flow actually needs to read text.
Pages that never call this never download either.
Quick start
Add a trigger button with a data-capture-type. Same script tag, no extra include:
<button type="button" data-jsscanner-capture-trigger
data-capture-type="swiss_id">
Scan ID Card
</button>
<script src="https://your-app.test/embed/YOUR_TOKEN.js"></script>
For a credit card, use data-capture-type="credit_card" instead.
The two flows
Swiss ID card
The MRZ is on the back, not the front. The Swiss ID card's front only carries the
photo and the printed fields around it (name, dates, sex, nationality, document number) — the 3-line
machine-readable zone is on the reverse side. Both sides are captured before anything is verified.
- Camera opens with a card-shaped guide frame for the front. While the card sits in
the frame, a live loop keeps checking it: once the image has held sharp for a moment, it tries one
read attempt itself — no shutter tap needed. A green "Looks good!" confirms it and moves on
automatically; the manual shutter is always there too, for a card the auto-read can't confirm
(glare, low light).
- An interstitial screen ("Front captured — now turn the card over") gives you a moment to physically
flip the card before the back's camera screen opens.
- Same live-loop capture for the back — except here a "successful read" is a real
check: the 3-line machine-readable zone (MRZ, ICAO 9303 TD1 format) is OCR'd and every check digit
verified (document number, date of birth, date of expiry, composite). Only a genuinely valid MRZ
triggers the auto-accept; anything else keeps waiting, or falls back to the manual shutter, which
re-checks the same way and prompts a retake if it still doesn't validate.
- The front's normal printed text is separately OCR'd and cross-checked against the MRZ-derived name
and document number, purely as an informational match/mismatch indicator.
- A review screen shows every MRZ-derived field, pre-filled and editable, before confirming.
The front's auto-read is a heuristic, not a verification. There's no checksum on the
front to check against — "looks good" there just means enough text was found to plausibly be a filled-in
ID. The back's auto-read is the real gate: it only fires on a checksum-valid MRZ, identical to what the
manual-shutter path checks afterward.
Credit card
- Camera opens with the same card-shaped guide frame; only the back of the card is captured.
- The back is OCR'd and pattern-matched for cardholder name, card number, expiry date and CVC.
- A review screen shows all four fields, pre-filled and editable, before confirming.
Customization
| Attribute | Default | Effect |
data-capture-type | — | Required. swiss_id or credit_card. |
data-capture-accent | #fb476a | Highlight color for the guide frame and primary buttons. |
data-jsscanner-oncapture | — | Name of a global function to call with the finished result (see below). |
<button type="button" data-jsscanner-capture-trigger
data-capture-type="credit_card"
data-capture-accent="#00c2a8">
Scan Card
</button>
Getting the data
Same three capture methods as scan data, all firing with the same payload shape: { type, data, images }.
DOM event
document.addEventListener("jsscanner:capture", (event) => {
const { type, data, images } = event.detail;
// type === "swiss_id" | "credit_card"
// images is { front, back } for swiss_id, { back } for credit_card — each a Blob
console.log(type, data);
});
Global function name
<button type="button" data-jsscanner-capture-trigger
data-capture-type="swiss_id"
data-jsscanner-oncapture="handleCapture">
Scan ID Card
</button>
<script>
function handleCapture(result, meta) {
fetch("/my-api/onboarding", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ type: result.type, data: result.data }),
});
}
</script>
JavaScript callback
document.addEventListener("jsscanner:ready", () => {
document.getElementById("my-button").addEventListener("click", () => {
JSScannerEmbed.openDataCapture({
type: "credit_card",
accentColor: "#00c2a8",
onCapture(result, meta) {
console.log(result.data.cardNumber, result.data.expiry);
},
});
});
});
Result shape
data for swiss_id:
{
surname: "MUSTER",
givenNames: "MAX",
documentNumber: "S1234567X",
dateOfBirth: "1990-04-12",
sex: "M",
nationality: "CHE",
dateOfExpiry: "2030-04-11",
mrzVerified: true // false if the MRZ checksum couldn't be verified and the operator chose "Continue anyway"
}
data for credit_card:
{
name: "MAX MUSTER",
cardNumber: "4111111111111111",
expiry: "04/30",
cvc: "123"
}
Every field above passed through the review screen, so it reflects whatever the operator confirmed —
not necessarily the raw OCR output.
Good to know
- All OCR runs client-side via Tesseract.js, loaded from a pinned CDN version the first time a flow needs it — no image or extracted text is ever sent to our servers. The engine and its language data are cached by the browser after the first use, so subsequent scans on the same device start faster.
- The Swiss ID MRZ check is a real ICAO 9303 checksum validation, not a heuristic — if it passes, the document number, birth date and expiry date are verified against tamper-evident check digits. The front-vs-visual-text cross-check and everything in the credit card flow are best-effort pattern matching, not verification.
- Reading a credit card's back varies a lot by issuer — not every card prints the same fields in the same place, and small italic CVC text is the least reliable part to OCR. That's exactly why every flow ends on an editable review screen rather than returning data straight from OCR.
- Like document scanning, this module is not tied to the usage/session tracking shown on your dashboard.