Basic Concepts

Last updated
December 19, 2025

Introduction

This section provides a structured reference for integrating the Saphere Scan widget into your application. Whether you're working on a web platform or a mobile environment, Saphere Scan offers a consistent API surface and lightweight deployment.

Integration Overview

  • Web: Embed the widget into any HTML page using the CDN script.
  • React Native: Use a local HTML file rendered inside a WebView.
  • Ionic: Integrate similarly to React Native via WebView.

Communication Flow Summary

Below is a high-level sequence diagram that illustrates the communication between the Saphere Scan widget on the frontend and the Saphere Core backend:

Communication Flow Summary

Integration Handler

The Handler object is the primary interface for mounting, unmounting, and configuring the Saphere Scan widget.

interface Handler {
  load: (container: string | HTMLDivElement, options: Options) => void;
  destroy: () => Promise<void>;
  langs: Lang[];
}

Properties

load(container, options)

  • Mounts the widget in the provided container.
  • container: Either a CSS selector string or a HTMLDivElement.
  • options: A configuration object (see Options reference).
  • Example:
Handler.load("#container", options);

destroy()

  • Asynchronously unmounts and removes the widget from the DOM.
  • Returns a Promise<void> that resolves once the widget is cleaned up.
  • Example:
await Handler.destroy();

langs

  • Returns an array of available language codes supported by the widget.
  • Example output:
["ar", "de", "en", "es", "fr", "it", "pl", "pt", "pt_BR", "tr"]

Integration Options

Full Options Reference

The full configuration object passed to Handler.load() looks like this:

interface Options {
  createMeasure: CreateMeasureOptions;
  onEvent?: (event: Event) => Promise<boolean | undefined>;
  allowLeave?: boolean;
  lang?: string;
  pages?: PageOptions
}

  • createMeasure (required): Defines how the measure is created (delegate or handle strategy).
  • onEvent: Callback that listens to events during the scan (start, result, aborted, etc.).
  • allowLeave: Show/hide the "Leave" button in the widget.
  • lang: Set the default language (en, fr, es, etc.).
  • pages: specify which pages to display or not.

Create a measure

You can create a measure using one of two strategies:

Delegate Strategy (recommended)
Use this strategy to delegate the retrieval of the measurement to the widget. You must provide an authenticated endpoint (e.g., via an API key or token). The widget will make a POST request to that URL to create a new measurement.

  • Delegate: Most common approach; widget will POST to a secure URL on your backend to create a measure.
createMeasure: {
  strategy: "delegate",
  url: "https://api.test.saphere.ai/measures",
  headers: { Authorization: `Bearer <YOUR_API_KEY>` }
}

This approach is fast and secure but requires proper access control on your backend.

Handle Strategy (advanced/custom logic)
Instead of calling a remote URL, you provide a function (fetch) that is responsible for returning a new measurement ID. This is useful if you want to run custom checks or workflows before the scan starts.

  • Handle: Advanced control; your frontend provides a fetch() function returning a measure object.
createMeasure: {
  strategy: "handle",
  fetch: async (options: FetchOptions) => {
    // custom logic here
    return { id: "...", ... }
  }
}

Sell also FetchOptions

Event Handling Overview

The Saphere Scan widget is designed around an event-driven integration model.
Rather than exposing imperative APIs, the widget communicates its internal state, user interactions, and backend outcomes exclusively through events.

Integrators are expected to listen to these events and react accordingly—whether to update their UI, persist results, handle errors, or control navigation in the host application.

How Events Are Emitted

There are two distinct event channels:

  • SDK lifecycle events, emitted as DOM events (e.g. Handler Ready)
  • Widget runtime events, delivered through the onEvent callback

Only runtime events appear in the onEvent stream. SDK lifecycle signals must be handled separately using window.addEventListener.

Understanding the Event Flow

The diagram below illustrates the complete lifecycle of the widget, from SDK initialization to scan completion.

It is organized around:

  • A single vertical main flow, representing the normal scan lifecycle
  • Side deviations, representing early exits (leave) and terminal failures (aborted)
  • A clear separation between frontend-controlled phases and backend-controlled processing
The Event Flow

Events Reference

This section describes all events emitted by the Saphere Scan, how they are delivered, when they may occur, and how integrators should interpret them.

Event Channels Overview

There are two distinct event channels in the SDK.

SDK Lifecycle Events (DOM Events)

Some lifecycle signals belong to SDK initialization, not widget execution.
These are emitted as DOM events on window and are not delivered through onEvent.

Widget Runtime Events (onEvent)

All widget behavior (camera, scan lifecycle, results, failures) is reported through the onEvent callback.

type OnEventOptions = (event: Event) => Promise<boolean | void>
SDK Lifecycle Event (DOM)
⚡ Handler Ready
  • Delivery
  • DOM event (window.addEventListener)
  • ❌ Not available in onEvent
window.addEventListener("handler-ready", () => {  
	// Saphere Scan widget is ready
})

When

  • SDK script is loaded
  • Internal handlers are initialized

Guarantees

  • The widget can now be mounted
  • Runtime events may now be emitted
⚡ video-stream-loading:start
{
  type: "video-stream-loading",
  status: "start"
}

Meaning

  • The widget is requesting camera access
  • The browser permission prompt may appear
video-stream-loading:ready
{
  type: "video-stream-loading",
  status: "ready"
}

Meaning

  • Camera access is granted
  • The video stream is usable
  • The user may start a scan
⚡ video-stream-loading:error
{
  type: "video-stream-loading",
  status: "error",
  reason
}

Meaning

  • Camera access failed
  • A scan cannot be started

Possible reasons

  • denied
  • no-device
  • already-used
  • not-supported
Scan Lifecycle Events (onEvent)

Scan lifecycle events represent the main execution flow from user interaction to backend processing.

⚡ start
{ type: "start" }

When

  • The user clicks the “Start” button

Guarantees

  • A scan workflow has started

Does NOT guarantee

  • That the scan will succeed
⚡ record
{ type: "record" }

When

  • Video capture effectively begins

Meaning

  • Signal acquisition is running
  • User stability is required
  • A measure exists
⚡ end
{ type: "end" }

When

  • Video capture finishes

Important

  • This does not indicate success
  • The scan may still be aborted afterward
  • The widget may still be sending buffered data
  • The backend will start computation only after receiving the full dataset
  • This event marks the end of capture, not the end of data transmission
⚡ result (Terminal Success)
{
  type: "result",
  id,
  variables
}

When

  • Backend processing completed successfully

Guarantees

  • The scan exists and is persisted
  • This is the only success signalNo further scan events will follow
Deviation Events (onEvent)

Deviation events represent early exits or failures and are not part of the normal scan flow.

⚡ leave — User Exit
{ type: "leave" }

When

  • The user explicitly clicks the “Leave” button
  • Can occur any time after Handler Ready

Semantics

  • A scan may or may not exist
  • This is not an error
  • The widget is intentionally exited

Typical usage

  • Close widget
  • Restore host application UI
  • Trigger custom navigation
⚡ aborted — Scan Failure
{
  type: "aborted",
  reasons
}

When

  • A scan has started
  • The scan terminates before a result is produced

Can occur

  • During countdown
  • During scan
  • After end, while waiting for backend processing

Guarantees

  • No result will ever be emitted
  • The scan does not exist
  • A restart is required
Abort Reasons

Abort reasons explain why the scan could not complete.
They are terminal and always require a restart.

User & Interaction Abort Reasons
  • FOCUS_LOST: Page or tab lost focus
  • WIDGET_RESIZED: The widget has been resized during the capture process
  • CANCELED_WHILE_COUNTDOWN: The user clicked on the cancel button during the countdown step
  • CANCELED_WHILE_CAPTURING: The user clicked on the cancel button during the capturing step
  • CANCELED_WHILE_SENDING: The user clicked on the cancel button during the sending step
  • CANCELED_WHILE_PROCESSING: User canceled during backend processing
  • COMPONENT_UNMOUNTED: Widget removed from DOM
Technical Abort Reasons
  • CONNECTION_ERROR: Network failure
  • UNEXPECTED_WS_CLOSED: Connection with backend unexpectedly closed
  • UNEXPECTED_WS_RESULT: Invalid backend response
  • CREATE_MEASURE_ERROR: Measure creation failed
  • VIDEO_SOURCE_ENDED: Camera stream ended unexpectedly
Early Conformity Abort Reasons
  • CONFORMITY_POOR_LIGHT: Lighting insufficient
  • CONFORMITY_LOW_FPS: Frame rate too low
  • CONFORMITY_FACE_PRESENCE: Face not detected
  • CONFORMITY_MUCH_VARIATIONS: Excessive movement
Result Interpretation & Backend Errors

Receiving a result event means the scan completed, but not all variables are guaranteed to be valid.

Variable-Level Result Model

Each variable follows this structure:

type VariableValue =
  | { value: ValueType; error: null }
  | { value: null; error: Conformitie | ConformitieBp }

This allows partial success.

Conformity Errors in Results
  • Conformity errors indicate data quality issues, not scan failure
  • CONFORMITY_POOR_LIGHT: Insufficient light
  • CONFORMITY_LOW_FPS: The device is too slow to process the capture
  • CONFORMITY_FACE_PRESENCE: Face is lost
  • CONFORMITY_MISSING_SIGNAL: Physiological signal is missing
  • CONFORMITY_FACE_SIZE: Face too small
  • CONFORMITY_INITIALISATION:
Input & Business Errors
  • MISSING_USER_DATA_*: Required user data missing
  • CREDIT_EXCEEDED: Account limit reached

Languages

The widget supports multiple languages. Use the lang option to enforce a default:

lang: "en" // or "fr", "de", "pt", etc.

Available values: ar, de, en, es, fr, it, pl, pt, pt_BR, tr

User Data Input Options

Some physiological variables (e.g., BMI from face Scan, cardiovascular health score, high blood pressure risk) require additional user context like age, sex, weight, height, or smoking status. The widget supports two integration modes to handle this information:

1. Programmatic Injection (userData via FetchOptions)

If you already collect user data in your application (e.g., from a sign-up form or user profile), you can inject it directly when creating a measure.

Example:

createMeasure: {
  strategy: "handle",
  fetch: async (options) => {
    console.log(options.userData) // auto-populated if using form, or inject it yourself

    return await createMeasureInBackend({
      userData: {
        age: 30,
        sex: "M",
        weight: 72,
        height: 178,
        smokingStatus: "non-smoker"
      }
    });
  }
}

This approach gives you full control over the user experience and data validation. You must ensure all required fields are present to prevent aborted scans due to MISSING_USER_DATA_* errors.

2. UI-Based Collection (via User Data Form)

The User Data Form allows collecting user metadata directly through the Saphere UI before a measurement.
This form is activated and configured through the pages field of the loading (or integration) Options object.

The User Data Form
Enabling the User Data Form

To enable the User Data Form, define a pages.validates configuration in load(or integration) options.
The form page can be configured to show or hide specific form fields.

Example configuration
"pages": {
  "validates": {
    "3": {
      "ignore": false,
      "fields": {
        "height": { "ignore": false },
        "weight": { "ignore": false },
        "age": { "ignore": false },
        "sex": { "ignore": false },
        "smokingStatus": { "ignore": false },
        "externalId": { "ignore": false }
      }
    }
  }
}
Configuration behavior
  • Each key under validates represents a UI page index
  • Setting ignore: false on a page activates the User Data Form for that page
  • Each field can be individually controlled using its own ignore flag
Field visibility control

Clients can choose which form fields are displayed by toggling their ignore value:

  • height: User height
  • weight: User weight
  • age: User age
  • sex: User sex
  • smokingStatus: User smoking status
  • externalId: Client-defined external identifier
Notes

Note

"age": { "ignore": true }

The field will not be shown in the User Data Form.

Note

At least one field must be enabled (ignore: false) for the User Data Form to be displayed.

Once enabled:

  • The form will be shown before measurement begins
  • The widget will validate the inputs
  • The form-submitted userData will:
    • Be passed to your fetch() function (if using handle strategy)
    • Be included in the POST body (if using delegate strategy)

🔐 Note: You're still responsible for using the collected data to create the measure on the backend.

👉 See also Metrics Dependencies Overview.

👉 See also how to Create and Apply Customization.

FetchOptions Reference

When using the "handle" strategy in createMeasure, your custom fetch() function receives a FetchOptions object. This object lets the widget pass relevant user context and requested variables.

interface FetchOptions {
  userData?: UserData;
  desiredVariables?: Variable[];
  unwantedVariables?: Variable[];
}

Fields

userData (optional)

Contains user-specific information such as:

interface UserData {
    externalId?: string
    age?: number;
    sex?: "M" | "F";
    weight?: number;
    height?: number;
    smokingStatus?: "smoker" | "non-smoker";
}

📌Note: These values depend on how you configure the user data form (or feed data manually).

externalId (optional)

Type: string

A client-defined identifier used to associate a Saphere request or result with an external system, user, or business context.

The value is stored and returned unchanged by the Saphere API and can be used to correlate Saphere resources with client-side data or workflows.

Use cases

  • Link measurements or analyses to internal user IDs
  • Track requests across distributed systems
  • Simplify reconciliation, logging, or debugging

Example

{
  "externalId": "user-12345-abcde-6789"
}

👉 See also Metrics Dependencies Overview.

desiredVariables (optional)

An array of variables that the client wants to compute (e.g., ["hr", "br", "faceBmi"]). Use this to restrict or validate what’s computed server-side.

By default, all variables included in your subscription are automatically calculated.

You can refine this selection using the desiredVariables (variables you want) and unwantedVariables (variables to exclude) parameters.

👉 See also Available Metrics

unwantedVariables (optional)

Same as above, but for excluding variables explicitly. If you receive both desiredVariables and unwantedVariables, desiredVariables takes precedence.

Example usage

fetch: async (options: FetchOptions) => {
  console.log("User wants:", options.desiredVariables);
  console.log("User data:", options.userData);
  const res = await fetch("/api/create-measure", {
    method: "POST",
    body: JSON.stringify(options),
    headers: { "Content-Type": "application/json" }
  });
  return await res.json(); // Must return a valid CreatedMeasureDTO
}

📌Note: If you fill both desiredVariables and unwantedVariables, desiredVariables takes precedence.

Create Your Proxy

To use the widget in delegate mode, your application must expose a server-side proxy that securely handles the creation of measurement sessions.

This proxy acts as an intermediary between the Saphere Scan frontend and the Saphere Core backend, enabling secure authentication and control over who can start a scan.

Proxy integration

Why a Proxy?

  • The frontend (e.g. browser) should never expose API keys directly.
  • The widget needs to retrieve a measurement token before it can open a WebSocket.
  • Your backend will handle authentication and access control before requesting the token from Saphere Core.

Expected Proxy Behavior

Your backend must implement an HTTP endpoint that accepts the measurement request. It should forward the request to the official Saphere Core API, adding the required headers:

POST /api/my-measure-proxy
Authorization: Bearer <your-backend-token>
Content-Type: application/json

{
  "userData": {
    "age": 30,
    "height": 175,
    "weight": 68,
    "sex": "M"
  },
  "desiredVariables": ["hr", "hrv", "bpClass"]
}


This endpoint will forward the request to:

POST https://api.test.saphere.ai/measures

And return the CreatedMeasureDTO object to the widget.

Proxy Benefits

  • Protects your credentials
  • Allows custom validation and logging
  • Enables quota and user tracking
  • Keeps your frontend lightweight and secure

📌 Note: If you're using the handle strategy instead of delegate, your proxy is implemented directly inside your frontend logic using a fetch() function.

Close Modal