Hosted link events
A hosted verification page (https://trust.myaza.co/verify/…) tells whatever is hosting it what the applicant is doing: when the flow is ready, which step they are on, when they submit, and when something goes wrong. Nothing to install. Open the link in a WebView (or an iframe) and listen.
The events are deliberately light. They carry ids, step names and error codes, never anything the applicant typed or captured. The result itself still arrives by webhook and GET /status/:id, exactly as before.
Which link to open
Mint a per-applicant session from your backend with POST /api/kyc/sessions and open its url. That link belongs to one person and carries your externalUserId, so every event you receive is already about a user you know. The sessionId it returns is also the verification's id once the applicant submits.
A shared hosted link (/verify/workflow/<linkToken>) emits the same events, but a shared link is one URL for many people, so the events cannot tell you who is verifying.
The events
Every message is a JSON object with source: "myaza-kyc" and a type:
type | When | Fields |
|---|---|---|
ready | The page loaded the session and is about to show the first step. | sessionId, environment, subjectType, scope |
started | The flow mounted. | sessionId |
step | The applicant reached a step, including the one the flow opened on. | sessionId, step |
submitted | The applicant submitted. Processing continues on the server. | sessionId, verificationId, status (pending) |
completed | A returning applicant opened a link whose verification was already submitted. | sessionId |
error | A technical error (network, camera permission, expired link). | sessionId, code, message |
closed | An embedded flow's Done or close was pressed. | sessionId |
step values are the SDK's step names: consent, email-verification, phone-verification, country-select, id-type, id-input, document-capture, liveness, proof-of-address, address-search, address-collection, address-entrance, address-review, questionnaire, business-details, business-key-people, business-documents, applicant-role, submitted. error codes are the SDK's KYCError codes (network_error, camera_permission_denied, upload_failed, feature_disabled, insufficient_credits, invalid_api_key, unknown).
New event types may be added; existing ones are never renamed. Ignore types you do not recognise.
React Native (react-native-webview)
The page posts to the WebView's own channel, so there is nothing to configure on the page side:
import { WebView } from 'react-native-webview';
<WebView
source={{ uri: session.url }}
allowsInlineMediaPlayback
mediaPlaybackRequiresUserAction={false}
onMessage={(event) => {
const message = JSON.parse(event.nativeEvent.data);
if (message.source !== 'myaza-kyc') return;
if (message.type === 'submitted') {
// message.verificationId is the id to poll or match against your webhook.
navigation.replace('VerificationPending', { id: message.verificationId });
}
if (message.type === 'error') console.warn('KYC error', message.code);
}}
/>The camera works inside react-native-webview on iOS 14.3+ and on Android once your app grants the WebView's camera permission request (onPermissionRequest). NFC chip reading, background presence checks and native liveness are not available inside a WebView; if your workflow needs them, use the React Native SDK.
Flutter (webview_flutter)
Register a JavaScript channel named MyazaKYC; the page posts to it when it exists:
final controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel('MyazaKYC', onMessageReceived: (message) {
final event = jsonDecode(message.message) as Map<String, dynamic>;
if (event['source'] != 'myaza-kyc') return;
if (event['type'] == 'submitted') {
// event['verificationId']
}
})
..loadRequest(Uri.parse(session.url));In an iframe
Append your page's origin to the link as origin, and the page posts to that window only when the browser confirms it really is the embedder:
<iframe src="https://trust.myaza.co/verify/<token>?origin=https://app.example.com" allow="camera; microphone; geolocation"></iframe>window.addEventListener('message', (event) => {
if (event.origin !== 'https://trust.myaza.co') return;
if (event.data?.source !== 'myaza-kyc') return;
// event.data.type, event.data.step, event.data.verificationId …
});An origin that does not match the page's actual parent is ignored: no events are posted at all, rather than to a window that should not read them.
What the events are not
They are a courtesy to the host's UI, not the record. Treat submitted as "the applicant is done with the flow"; the verdict still comes from your webhook or from polling GET /status/:id with the sessionId you minted.