Events
The widget communicates with your host app only via callbacks you pass in the config. There's no shared DOM state to poll, no postMessage to intercept, no global bus. If you want to know what the widget is doing, listen to its events.
onReady
Fires once, after the widget has mounted, fetched initial resources (sponsor config, provider products), and rendered the first step.
Signature: () => void
Use it to hide your app's loading spinner or to log a widget-ready metric.
onStepChange
Fires every time the widget moves to a new step. Purely informational — mostly useful for analytics or progress bars in your host app.
Signature: (step: { name, index, total }) => void
Step names (stable identifiers you can switch on):
search_beneficiaryselect_productscapture_indicationreview_and_submitotp_challengeoutcome
onComplete
Fires once, when the beneficiary finishes the flow with an outcome (approved, rejected, or async-pending). After onComplete fires, no further step-changes are emitted.
Signature: (outcome: PreAuthOutcome) => void
Payload shape
type PreAuthOutcome = {
authorization_request_id: string;
status: 'COMPLETED' | 'PENDING_SPONSOR';
authorizations: Array<{
authorization_code: string;
product_type: 'MEDICATION' | 'LAB' | 'IMAGING' | 'PROCEDURE';
status: 'APPROVED' | 'REJECTED' | 'PENDING_SPONSOR';
rejection_reason?: string;
expires_at?: string; // ISO 8601
products: Array<{ product_code: string; quantity: number }>;
}>;
duration_ms: number; // Widget wall-clock time
telemetry_id?: string; // The trackingId you set in config
};
status: 'COMPLETED'— Sponsor evaluated synchronously.authorizationsis populated with real outcomes.status: 'PENDING_SPONSOR'— Sponsor took the request but is processing offline.authorizationsmay include placeholder entries withstatus: 'PENDING_SPONSOR'. Listen for theauthorization.createdwebhook to get the final decision.
Handling the outcome
onComplete: (outcome) => {
if (outcome.status === 'PENDING_SPONSOR') {
showToast('Sponsor is processing your request — we\'ll notify you.');
persistPendingRequest(outcome.authorization_request_id);
return;
}
outcome.authorizations.forEach((auth) => {
if (auth.status === 'APPROVED') {
renderQrCode(auth.authorization_code);
} else {
showRejection(auth.rejection_reason);
}
});
}
onError
Fires on any unrecoverable error. The widget stays mounted (so the user can retry if it's a network error) but no further progress is possible without intervention.
Signature: (err: PreAuthError) => void
Payload shape
type PreAuthError = {
code: string; // Osigu error_code, e.g. '071-104'
message: string; // Human-readable
step: string; // Which step raised it (see onStepChange step names)
recoverable: boolean; // true if a retry is possible
cause?: unknown; // Underlying error (network, parse, etc.)
};
Common codes:
071-104— Product not registered against your provider.071-107— Sponsor rejected the request outright (before individual authorization decisions).071-201— OTP flow failed after 3 attempts.WIDGET-NETWORK— HTTP failed after retries.WIDGET-TOKEN—getTokenreturned invalid data or the token is expired.
onClose
Fires when the user actively dismisses the widget (X button, ESC key, or clicks outside if you enabled backdrop dismissal). Does not fire on onComplete or onError.
Signature: () => void
Use it to release any UI state your host app was tracking (progress indicators, cancelled analytics events, etc.).
Ordering guarantees
onReadyfires first, always.onStepChangemay fire multiple times, always afteronReady.- Exactly one of
onComplete/onError/onClosefires last. - Once the terminal callback has fired, no further callbacks will fire — even if the user interacts with the widget's DOM afterwards.
Related
- Configuration — where you wire the callbacks.
- Troubleshooting — decoding
onError.codevalues.