Options

Everything Handler.load() accepts: the measure strategy, the event callback, the eleven-colour palette, the screens, and the texts.

Legacy module — no longer recommended

This page documents the v1 widget, kept for integrations already in production. New integrations should use Saphere Scan v2.

New integrations should use Saphere Scan v2. What follows documents the v1 options object.

Your options are validated, then merged over the defaults — and over any customisation applied by ?customizationId=. Unknown top-level keys are rejected rather than ignored.

Top level

KeyTypeDefaultNotes
createMeasureobjectRequired. How a measure is obtained.
onEventfunctionCalled on every event.
allowLeavebooleanfalseShows a control letting the user quit the widget.
allowSkipbooleanfalseLets the user skip onboarding screens.
langstringbrowser language, else "en"One of the ten supported languages.
useShadowbooleantrueMounts inside a shadow root.
colorsobjectsee belowEleven hex colours.
textsobjectbuilt-inPer-language string overrides.
pagesobjectsee belowWhich screens appear, and how.
desiredVariablesstring[]Narrows what is computed.
tagsstring[]Stored with the measure.

`useShadow: false` lets your CSS reach inside

The default mounts the widget in a shadow root, which stops your stylesheet from affecting it and its own from affecting you. Turning that off is occasionally necessary in hybrid shells; when you do, expect your global CSS to reach the widget’s internals.

createMeasure

The widget needs a measure to attach the capture to. There are two strategies, and they differ in who makes the call.

delegate — the widget calls your endpoint

createMeasure: {
    strategy: "delegate",
    url: "/api/saphere-measure",
    headers: { "X-Session": sessionId }   // optional
}

The widget issues POST <url> with a JSON body carrying userData, desiredVariables and tags, and expects { "id": "<measureId>" } back.

Your endpoint authenticates your user, calls POST /measures on the Saphere API with your key, and relays the identifier.

handle — your code makes the call

createMeasure: {
    strategy: "handle",
    fetch: async ({ userData, desiredVariables }) => {
        const response = await fetch("/api/saphere-measure", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ userData, desiredVariables })
        })
        return await response.json()      // must be { id: "…" }
    }
}

`fetch` must resolve to an object with an `id`

Returning the identifier as a bare string throws Cannot get measureId!. The contract is { id: string } — an object.

This is worth checking first when a v1 integration fails at the very start: some published examples returned json["token"], which is a string, and does not satisfy it.

If the endpoint answers with a non-2xx status, the widget throws Cannot create measure!.

onEvent

onEvent: async event => {
    console.log(event.type, event)
}

Called for every event below. The return value is meaningful: returning literally false cancels the transition the event announces. Anything else — including undefined, and including a thrown exception, which is logged and swallowed — allows it.

The handler is awaited.

A slow handler slows the widget

Because the return value can veto a transition, v1 waits for your handler before proceeding. A handler that performs a network call on every event delays the user journey by that call.

This is the behaviour v2 deliberately dropped: there, onEvent is never awaited, and vetoing moved to a separate hooks API with a bounded timeout.

The event union

typePayloadWhen
video-stream-loading{ status: "start" }The camera is being requested
video-stream-loading{ status: "ready" }The stream is open
video-stream-loading{ status: "error", reason }The camera was refused
startThe face is accepted and framing is frozen
recordCapture begins
endAll frames are buffered and the end marker is queued
result{ id, userData?, variables }The measurement completed
aborted{ reasons }The measurement stopped
leaveThe user asked to quit (requires allowLeave)
transition{ from, to }Declared, but never emitted

video-stream-loading reason is one of "denied", "no-device", "not-supported", "already-used".

aborted reasons carries either a conformity code (CONFORMITY_POOR_LIGHT, CONFORMITY_MUCH_VARIATIONS, CONFORMITY_FACE_PRESENCE, CONFORMITY_LOW_FPS, CREDIT_EXCEEDED, MISSING_USER_DATA_*, …) or a failure reason (WIDGET_RESIZED, FOCUS_LOST, CANCELED_WHILE_CAPTURING, CREATE_MEASURE_ERROR, WS_CONNECTION_ERROR, VIDEO_SOURCE_ENDED, …).

`transition` never fires

TransitionEvent is part of the declared type, and a handler branching on it will compile. Nothing in the module constructs one. Do not build navigation tracking on it.

In a result event, each variable is { value, signals, error: null } on success, or { value: null, error } on failure.

colors

Eleven slots, each a hex string (#RGB or #RRGGBB). All eleven are required once you supply the object.

KeyDefaultRole
color01#002B49Primary colour
color02#FF585DSecondary colour
color03#EDF1FFPopup background
color04#FFE055Star colour
color05#002B49Primary button text
color06#FF585DPrimary button background
color07#002B49Secondary button text
color08#EDF1FFSecondary button background
color09#FFFFFFModule background
color10#002B49Face detection — valid
color11#FF585DFace detection — invalid

Only color01 and color02 are used to derive further tints and shades; the rest are applied literally.

pages

Which screens appear, and how.

pages: {
    notice: { ignore: false },
    logo: { ignore: false, data: null },
    staticFacePlacement: { ignore: false },
    result: { ignore: false, titles: { ignore: false } },
    validates: {
        1: { ignore: false }, 2: { ignore: false },
        3: { ignore: true },  4: { ignore: false },
        5: { ignore: false }, 6: { ignore: false },
        7: { ignore: false }, 8: { ignore: false }
    }
}
KeyNotes
noticeShows the notice button
logo{ ignore: true }, or { ignore: false, data } where data is an image source. null shows the built-in i-Virtual logo.
staticFacePlacementThe static face guide during placement
resultThe result screen; titles.ignore hides its headings

The eight onboarding screens

All eight are required when you supply validates.

ScreenContentDefault
1Introductionshown
2Data-protection noticeshown
3User-data formhidden
4Guidance — postureshown
5Guidance — uncovered face (mobile)shown
6Guidance — lightingshown
7Guidance — stillnessshown
8Guidance — finalshown

Screen 3 takes a fields object when enabled:

validates: {
    3: {
        ignore: false,
        fields: {
            height: { ignore: false },
            weight: { ignore: false },
            age: { ignore: false },
            sex: { ignore: false },
            smokingStatus: { ignore: false },
            externalId: { ignore: true }
        }
    }
}

All six keys are required, and they cannot all be ignore: true — a form with no fields is refused.

Removing guidance costs measurements

Screens 4 to 7 are what tell users to sit still, uncover their face and avoid backlighting. Removing them shortens the journey and lowers the proportion of measurements that produce a usable signal.

texts

Every user-visible string, overridable per language. The object must carry all ten language keys — ar, de, en, es, fr, it, pl, pt, pt_BR, tr — and each is merged over the built-in strings, so you override only what you name.

texts: {
    ...Handler.defaultModuleOptions.texts,
    en: {
        ...Handler.defaultModuleOptions.texts.en,
        button: { next: "Continue" }
    }
}

Spreading the defaults is the practical way to satisfy the requirement without restating ten languages by hand.