Skip to content

Events & Callbacks

The widget communicates through callbacks passed to Circadify.init(). All callbacks are optional but recommended for production use.

CallbackWhen it fires
onCompleteScan finished successfully
onErrorScan failed (camera, network, quota, etc.)
onCancelUser closed the modal before completion (button mode only)
onProgressPer-frame phase + percent updates
onQualityWarningLighting / motion / face-position issues during the scan

Fired when a scan completes successfully. Receives a WidgetResult object with the measured vital signs.

Circadify.init({
apiKey: 'ck_live_your_api_key_here',
onComplete: (result) => {
console.log('Heart Rate:', result.heartRate, 'BPM');
console.log('HRV:', result.hrv, 'ms');
console.log('Respiratory Rate:', result.respiratoryRate, 'breaths/min');
console.log('SpO2:', result.spo2, '%');
console.log('Blood Pressure:', result.systolicBp + '/' + result.diastolicBp, 'mmHg');
console.log('Confidence:', Math.round(result.confidence * 100) + '%');
},
});
interface WidgetResult {
heartRate: number; // Heart rate in BPM (always present)
hrv?: number; // Heart rate variability in milliseconds
respiratoryRate?: number; // Respiratory rate in breaths per minute
spo2?: number; // Blood oxygen saturation (percentage)
systolicBp?: number; // Systolic blood pressure in mmHg
diastolicBp?: number; // Diastolic blood pressure in mmHg
confidence: number; // Measurement reliability score (0–1)
sessionId: string; // Unique session identifier
timestamp: number; // Unix timestamp in milliseconds
}

Fields marked with ? are optional and may be undefined depending on signal quality or the vitals filter you configured. heartRate and confidence are always present.

Fired when a scan fails. Receives a standard Error object with a descriptive message property.

Circadify.init({
apiKey: 'ck_live_your_api_key_here',
onError: (error) => {
console.error('Scan failed:', error.message);
// Show a user-friendly message
switch (error.message) {
case 'CAMERA_PERMISSION_DENIED':
alert('Please allow camera access to measure your vitals.');
break;
case 'CAMERA_NOT_FOUND':
alert('No camera detected. Please connect a camera and try again.');
break;
case 'NETWORK_ERROR':
alert('Network error. Please check your connection and try again.');
break;
case 'INVALID_API_KEY':
console.error('Check your API key configuration.');
break;
default:
alert('Something went wrong. Please try again.');
}
},
});
Error MessageDescription
CAMERA_PERMISSION_DENIEDUser denied camera access or it was blocked by browser settings.
CAMERA_NOT_FOUNDNo camera device available on the user’s machine.
NETWORK_ERRORFailed to reach the Circadify API (session creation or result submission).
INVALID_API_KEYThe provided API key is invalid, revoked, or does not match the environment.
SESSION_TIMEOUTThe scan took too long and was automatically terminated.
WASM_LOAD_FAILEDThe WebAssembly processing module failed to load. Usually a network or browser compatibility issue.

Fired when the user closes the modal before the scan completes. No arguments are passed. This only applies in button mode (the default) where the scanner opens in a modal.

Circadify.init({
apiKey: 'ck_live_your_api_key_here',
onComplete: (result) => {
console.log('Scan complete:', result);
},
onCancel: () => {
console.log('User cancelled the scan.');
// Optionally show a message or track the event
},
});

A complete example handling all three scenarios:

<script src="https://cdn.circadify.com/widget.js"></script>
<script>
Circadify.init({
apiKey: 'ck_live_your_api_key_here',
onComplete: (result) => {
// Send results to your backend
fetch('/api/vitals', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
heartRate: result.heartRate,
spo2: result.spo2,
confidence: result.confidence,
sessionId: result.sessionId,
}),
});
},
onError: (error) => {
// Log error for debugging
console.error('Circadify scan error:', error.message);
// Show user-friendly notification
showNotification('Unable to complete scan. Please try again.');
},
onCancel: () => {
// Optional: track cancellation analytics
analytics.track('vital_scan_cancelled');
},
});
</script>

Fires during the readiness gate and capture phase with per-frame phase + percent. Use it to drive a custom progress UI (the modal already has one — this is for inline mode where you may render your own).

Circadify.init({
apiKey: 'ck_live_your_api_key_here',
onProgress: (event) => {
// event.phase: 'initializing' | 'readiness' | 'capturing' | 'uploading' | 'processing'
// event.percent: 0–100 within the current phase
document.getElementById('progress').style.width = `${event.percent}%`;
},
});

Fires when scan quality drops below the readiness threshold (lighting, motion, face position, occlusion). The widget already surfaces these in its own UI — wire this callback if you also want to log them or show your own toast.

Circadify.init({
apiKey: 'ck_live_your_api_key_here',
onQualityWarning: (warning) => {
// warning.type: 'lighting' | 'motion' | 'face_position' | 'occlusion'
// warning.severity: 'low' | 'medium' | 'high'
// warning.message: human-readable string
console.warn(`[${warning.severity}] ${warning.type}: ${warning.message}`);
},
});

Circadify.init() returns an instance handle with three methods you can call later:

const widget = Circadify.init({
apiKey: 'ck_live_your_api_key_here',
mode: 'button',
onComplete: (result) => console.log(result),
});
// Open the scan modal from your own button
document.getElementById('start-scan').addEventListener('click', () => widget.open());
// Close the modal mid-scan (cancels the in-flight scan)
document.getElementById('close-scan').addEventListener('click', () => widget.close());
// Tear down the widget completely (releases camera, removes the floating button)
widget.destroy();

In inline mode, widget.close() is the equivalent of “cancel the in-flight scan” — there’s no modal to close, but it stops the camera and emits onCancel (or onError with CANCELLED).