NeccessoryNeccessory
Web SDK

Quickstart

Init the Web SDK with your key, start a measurement, and read the result.

This page covers the on-device tier. For the cloud tier — where your backend mints a session token and the metrics are computed on our servers — see Cloud quickstart.

Minimal measurement

The browser needs only your key id to run a measurement. The secret never leaves your server: the page forwards the result to your backend, which signs and submits it.

<video id="cam" autoplay playsinline muted width="480"></video>
<p>Heart rate: <span id="hr">--</span> bpm</p>
<button id="start">Start</button>

<script type="module">
  import { createMeasurement } from '@neccessory/web-sdk';

  const m = createMeasurement({
    videoEl: document.getElementById('cam'),
  });

  m.init({
    keyId: 'nk_live_sRwG9z2hK2hdV4Il83J7Cw',   // safe to embed; the secret is not
  });

  m.onRealtime((update) => {
    // Live during capture: current heart rate + quality level only.
    if (update.heartRate != null) {
      document.getElementById('hr').textContent = update.heartRate;
    }
  });

  m.onResult(async (result) => {
    // Once, on completion — the full measurement result.
    await fetch('/your-backend/measurements', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(result),
    });
  });

  m.onError((err) => console.error(err.code, err.message));

  document.getElementById('start').addEventListener('click', () => m.start());
</script>

Keep your face in view and hold still. A live heart rate appears after about 10 seconds; the full measurement completes in roughly 30–35 seconds and arrives via onResult.

Your backend then signs the forwarded result with the secret and posts it to /sdk/v1/measurements — see Authentication for the canonical string and the NCS1 headers.

Submitting straight from the browser

m.submit(result) signs and submits without a backend, but it needs the secret in init() — which publishes it to everyone who opens your page. Use it only from a server runtime, or from a first-party page whose visitors you already trust with the key.

m.init({ keyId: 'nk_live_sRwG9z2hK2hdV4Il83J7Cw', secret: '…' });
m.onResult(async (result) => { await m.submit(result); });

A secret in the browser is a public secret

Anyone who opens devtools on your page can copy it and sign requests as you: submit fabricated measurements into your account, burn your rate limit, and pull model artifacts. There is no origin allowlist to fall back on — a signed request is accepted from anywhere, and Origin is a header only browsers bother to send. If the page is public, forward through your backend instead: mint a cloud session server-side, or submit the result from your own server.

The lifecycle

MethodEffect
createMeasurement({ videoEl })Create the measurement and bind it to the video element to read from. Optional previewEl and overlayEl canvases render the preview and face overlay.
m.init({ keyId, secret })Attach your key. Synchronous — it stores credentials, it does not open the camera. Omit secret if your backend submits results.
await m.start()Load the engine, open the camera, and begin the measurement.
await m.stop()Stop the current measurement and release the camera.
await m.submit(result)HMAC-sign the result and submit it. Requires a secret.

start() reports failures through onError rather than rejecting, so a single error path covers both startup and capture.

Callbacks

CallbackFiresPayload
onRealtime(cb)~1/second during capture{ heartRate, quality: { level, score } } — live values only.
onResult(cb)Once, on completionThe full measurement result.
onError(cb)On any error{ code, message } — no internal details.

See the Callbacks reference for exact payload shapes.

Next: Callbacks reference.