Events & Callbacks
Handle scan results, errors, progress, and cancellations from the Circadify Embed Widget
The widget communicates through callbacks passed to Circadify.init(). All callbacks are optional but recommended for production use.
| Callback | When it fires |
|---|---|
onComplete | Scan finished successfully |
onError | Scan failed (camera, network, quota, etc.) |
onCancel | User closed the modal before completion (button mode only) |
onProgress | Per-frame phase + percent updates |
onQualityWarning | Lighting / motion / face-position issues during the scan |
onComplete
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) + '%');
},
});javascriptWidgetResult Interface
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
}typescriptA confidence score above 0.7 indicates a reliable measurement. Below 0.4 suggests quality issues such as poor lighting, excessive movement, or partial face visibility. Consider prompting the user to retry if confidence is low.
Optional Vitals
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.
onError
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.');
}
},
});javascriptCommon Errors
| Error Message | Description |
|---|---|
CAMERA_PERMISSION_DENIED | User denied camera access or it was blocked by browser settings. |
CAMERA_NOT_FOUND | No camera device available on the user's machine. |
NETWORK_ERROR | Failed to reach the Circadify API (session creation or result submission). |
INVALID_API_KEY | The provided API key is invalid, revoked, or does not match the environment. |
SESSION_TIMEOUT | The scan took too long and was automatically terminated. |
WASM_LOAD_FAILED | Required SDK runtime assets failed to load. Usually a network or browser compatibility issue. |
onCancel
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
},
});javascriptThe onCancel callback is not fired in inline mode since there is no modal to close. To handle cancellation in inline mode, use the Circadify.stop() method programmatically.
Combining All Callbacks
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>htmlonProgress
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}%`;
},
});javascriptonQualityWarning
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}`);
},
});javascriptProgrammatic control
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();javascriptIn 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).
Next Steps
- Configuration — Full config reference
- Styling — Customize the widget appearance