Hosted flow & Embed SDK
OKIAS ships a ready-made, mobile-first capture experience — document photos, guidance and active liveness included. Run it two ways: redirect the user to the hosted link, or embed the same flow on your own domain with one script tag.
Either way the server-side contract is identical: create a verification with your API key, get back an id and a hosted_url, and receive the decision on a signed webhook. No camera code, no liveness SDK, no upload pipeline.
Choose an integration
| Option A — Redirect / link | Option B — Embed SDK | |
|---|---|---|
| Where the user is | On okias.io (or an in-app webview) | On your domain — the flow renders in an iframe on your page |
| Frontend work | None — send the user to hosted_url | One script tag + one function call |
| Progress events | — | Lifecycle callbacks + window message events |
| Best for | Fastest path to production, email/SMS links | Onboarding funnels where users never leave your site |
If you would rather run your own capture UI end to end and upload assets directly, use the API path in Verifications → Submit assets instead.
How it works
POST /v1/verifications returns an id and a hosted_url. Your API key never touches the browser.hosted_url. Option B: pass the id to OkiasKYC.render() (or OkiasKYC.open()) on your page.1 · Create the verification (server-side)
/v1/verificationscurl https://api.okias.io/v1/verifications \
-H "Authorization: Bearer $OKIAS_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "level": ["FULL_KYC"], "country": "pk", "end_user_ref": "user_8842" }'The response includes the verification id, the hosted_url and the liveness_challenge — the server-generated, single-use sequence of head-turn actions the hosted flow will guide the user through in order (see the liveness challenge):
{
"id": "cms80k9ix001moea2jxy64fgu",
"status": "PENDING",
"hosted_url": "https://okias.io/verify/cms80k9ix001moea2jxy64fgu",
"liveness_challenge": { "actions": ["LOOK_DOWN", "TURN_RIGHT", "LOOK_UP", "TURN_LEFT"] },
...
}Store the id against your user before handing off, so you can reconcile the incoming webhook later.
2A · Option A — Redirect to the hosted link
Send the user's browser to hosted_url (or open it in an in-app webview):
// After creating the verification, send the user to the hosted flow.
const verification = await createVerification(user);
// Store verification.id against your user so you can reconcile the webhook.
await db.users.update(user.id, { okiasVerificationId: verification.id });
// Redirect the browser to the hosted capture experience.
res.redirect(verification.hosted_url);2B · Option B — Embed on your own site
The embed SDK renders the same hosted flow inside an iframe on your page, so users never leave your domain. It is a dependency-free script (~4 KB) served from https://okias.io/embed/okias.js — no npm install, no build step.
Inline widget
Drop a container element on the page and call OkiasKYC.render() with the verification id from your server:
<script src="https://okias.io/embed/okias.js"></script>
<div id="kyc"></div>
<script>
OkiasKYC.render({
container: '#kyc',
id: 'cms80k9ix001moea2jxy64fgu', // verification id from your server
onReady: () => {}, // widget loaded
onStarted: () => {}, // user tapped "Get started"
onStep: (e) => {}, // e.step: 'document' | 'details' | 'selfie' | 'review'
onComplete: (r) => {}, // r.status: APPROVED | DECLINED | REVIEW | PENDING
onError: (e) => {}, // e.message
});
</script>Modal overlay
Prefer a dialog over an inline section? OkiasKYC.open() takes the same options (minus container) and overlays the flow centred on the page with a close button:
// Modal overlay instead of an inline widget — same options, plus onClose.
OkiasKYC.open({
id: 'cms80k9ix001moea2jxy64fgu',
onComplete: (r) => console.log(r.status),
onClose: () => console.log('widget closed'),
});Options
| Option | Type | Description |
|---|---|---|
| container | string | Element | Selector or element to render into. render() only. |
| id | string | The verification id returned by POST /v1/verifications. Required. |
| height | number | string | Optional fixed height for the inline widget. Omit it to let the widget auto-size (below). |
| dismissable | boolean | open() only. Set false to stop a backdrop click from closing the modal. Default true. |
Both calls return a handle with a destroy() method — call it to remove the iframe and detach listeners, e.g. when your SPA navigates away:
const widget = OkiasKYC.render({ container: '#kyc', id: 'cms80k9ix001moea2jxy64fgu' });
// Later — remove the iframe and detach listeners (e.g. on SPA route change):
widget.destroy();Events & callbacks
The widget reports its lifecycle back to your page. Callbacks first:
| Callback | Fires when |
|---|---|
| onReady() | The flow finished loading inside the iframe. |
| onStarted() | The user tapped “Get started”. |
| onStep(e) | The user reached a step — e.step is document, details, selfie or review. |
| onComplete(r) | The flow finished — r.status is APPROVED, DECLINED, REVIEW or PENDING. |
| onError(e) | Submission failed — e.message explains why. |
| onClose() | The modal was closed (modal only). |
Window message events
The same lifecycle also arrives as message events on window, tagged source: 'okias-kyc' — useful if you listen outside the SDK (analytics, frameworks with their own event plumbing):
window.addEventListener('message', (event) => {
// 1 · Only trust messages from the OKIAS origin.
if (event.origin !== 'https://okias.io') return;
// 2 · Only handle widget messages.
const msg = event.data;
if (!msg || msg.source !== 'okias-kyc') return;
// msg.type: 'ready' | 'started' | 'step' | 'completed' | 'error' | 'close' | 'resize'
if (msg.type === 'completed') {
console.log(msg.status); // APPROVED | DECLINED | REVIEW | PENDING
}
});| type | Meaning |
|---|---|
| ready | The flow loaded. |
| started | The user tapped “Get started”. |
| step | Step change — carries step. |
| completed | Terminal result — carries status. |
| error | Submission failed — carries message. |
| close | The flow asked to be closed (modal dismisses itself). |
| resize | Content height changed — carries height. |
event.origin !== 'https://okias.io' before reading the payload — the SDK already applies this check internally for its callbacks.Auto-sizing
The inline embed sizes itself: as the user moves through the flow the widget posts resize {height} messages and the SDK adjusts the iframe height to match, so there is no clipped content and no inner scrollbar. Pass height only if you need a fixed-size slot.
Camera & permissions
Liveness needs the camera, and the widget runs in a cross-origin iframe — so camera access must be delegated to the frame. The SDK does this for you: the iframe it creates carries allow="camera; microphone" automatically.
- If your site sends a
Permissions-Policyheader, it must allow OKIAS as well, or the browser blocks the camera before the iframe attribute is even consulted:
Permissions-Policy: camera=(self "https://okias.io"), microphone=(self "https://okias.io")- HTTPS is required. Browsers only expose the camera on secure origins — the embedding page must be served over HTTPS (localhost is fine during development).
Security model
- The verification id is single-user scope. It grants access to one verification for one end user — hand each user only their own id, and never reuse one across users.
- Trust decisions only from signed webhooks. Browser events (
onComplete,messageevents) are for UX — advancing your UI, showing a success screen. Anything a client sends can be forged; provision the user only after the signed webhook confirms the decision server-side. - Only
/verify/*is embeddable. The verification flow is the one route that may be framed by other sites; every other OKIAS page is protected withframe-ancestors 'self', so the dashboard and console can never be embedded. - Your API key stays server-side. The browser only ever sees the verification id — never a secret key.
Under the hood: hosted endpoints
Inside the flow, the page talks to a public, unauthenticated hosted surface — authorisation is the unguessable id in the link:
/v1/hosted/verifications/:id/v1/hosted/verifications/:id/submitYou do not normally call these yourself — the hosted page (and the embedded widget) does. They are documented so you understand what happens under the hood.
curl https://api.okias.io/v1/hosted/verifications/cms80k9ix001moea2jxy64fguGet the decision
When the pipeline resolves, the verification reaches a terminal status — APPROVED, REVIEW, DECLINED or ERROR — and a signed webhook fires. Reconcile it to your user via the id in the event (the id you stored at create time — the event payload does not carry end_user_ref). Set up delivery in Webhooks.