Hosted link events
Build with

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.

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
Build with

Every message is a JSON object with source: "myaza-kyc" and a type:

typeWhenFields
readyThe page loaded the session and is about to show the first step.sessionId, environment, subjectType, scope
startedThe flow mounted.sessionId
stepThe applicant reached a step, including the one the flow opened on.sessionId, step
submittedThe applicant submitted. Processing continues on the server.sessionId, verificationId, status (pending)
completedA returning applicant opened a link whose verification was already submitted.sessionId
errorA technical error (network, camera permission, expired link).sessionId, code, message
closedAn 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)
Build with

The page posts to the WebView's own channel, so there is nothing to configure on the page side:

tsx
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)
Build with

Register a JavaScript channel named MyazaKYC; the page posts to it when it exists:

dart
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
Build with

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:

html
<iframe src="https://trust.myaza.co/verify/<token>?origin=https://app.example.com" allow="camera; microphone; geolocation"></iframe>
js
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
Build with

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.