{"_id":"@alialjundi/web-sdk","name":"@alialjundi/web-sdk","dist-tags":{"latest":"0.6.0"},"versions":{"0.6.0":{"name":"@alialjundi/web-sdk","version":"0.6.0","type":"module","description":"Embed the Othento identity verification flow in any web app with a few lines of JavaScript.","license":"UNLICENSED","main":"./index.cjs","module":"./index.mjs","types":"./index.d.ts","exports":{".":{"types":"./index.d.ts","import":"./index.mjs","require":"./index.cjs"}},"sideEffects":false,"publishConfig":{"access":"public"},"engines":{"node":">=18"},"dependencies":{},"_id":"@alialjundi/web-sdk@0.6.0","gitHead":"3c0ae09eab6931483d8bb5a1ed95a44b2dafb2fc","_nodeVersion":"22.22.0","_npmVersion":"10.9.4","dist":{"integrity":"sha512-cVB+Dm8467jdDuIwVhJqETbPiZ0heSbpZbR2yJR/7KMoLfNrDmIsB8oaas0JIA8K4hMk4mnABGVDKzFoZezE0w==","shasum":"c371a47587d0c23822a49e597b9b3fe5b8e58554","tarball":"https://registry.npmjs.org/@alialjundi/web-sdk/-/web-sdk-0.6.0.tgz","fileCount":15,"unpackedSize":79917,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDelzQv++39hGFHjcMRFEtzkeMy4TMJtWTjF55HObSOlQIgL4jgCFGPc/BrtP2qMU9zz4Bdlsot/h+yxQ/OoP+e0OE="}]},"_npmUser":{"name":"alialjundi","email":"alikaljundi@gmail.com"},"directories":{},"maintainers":[{"name":"alialjundi","email":"alikaljundi@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/web-sdk_0.6.0_1781445635567_0.4912494435152204"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-14T14:00:35.454Z","0.6.0":"2026-06-14T14:00:35.714Z","modified":"2026-06-14T14:00:35.911Z"},"maintainers":[{"name":"alialjundi","email":"alikaljundi@gmail.com"}],"description":"Embed the Othento identity verification flow in any web app with a few lines of JavaScript.","license":"UNLICENSED","readme":"# @alialjundi/web-sdk\n\n> Drop-in identity verification for the web — document capture, OCR, face match,\n> and liveness — embedded in your app with a few lines of JavaScript.\n\nThe SDK mounts an isolated cross-origin iframe in modal or inline mode, drives\nit over a versioned `postMessage` bridge (origin-checked, never a wildcard\ntarget), and hands you a typed decision (`Approved` /\n`Declined` / `InReview`) when the verification finishes. The API surface\nmirrors the Android SDK 1:1 so error codes, session statuses, and analytics\nevents line up across platforms.\n\n- **Bundle size:** ~3.6 KB gzipped, zero runtime dependencies\n- **Distribution:** ESM + CJS + `.d.ts` declarations\n- **Frameworks:** framework-agnostic — works in plain HTML, React, Angular, Vue, Svelte, Next.js\n- **Compliance:** PCI-/SOC 2-conscious — no PII or biometric data ever crosses the host page\n\n---\n\n## Contents\n\n1. [Requirements](#requirements)\n2. [Install](#install)\n3. [Quick start](#quick-start)\n4. [Configuration reference](#configuration-reference)\n5. [Lifecycle & callbacks](#lifecycle--callbacks)\n6. [Status & decision values](#status--decision-values)\n7. [Error codes](#error-codes)\n8. [Presentation: modal vs inline](#presentation-modal-vs-inline)\n9. [Cancellation reasons](#cancellation-reasons)\n10. [Cleaning up in SPAs](#cleaning-up-in-spas)\n11. [Content Security Policy & permissions](#content-security-policy--permissions)\n12. [Testing your integration](#testing-your-integration)\n13. [Troubleshooting](#troubleshooting)\n14. [TypeScript](#typescript)\n15. [Browser support](#browser-support)\n16. [Versioning & changelog](#versioning--changelog)\n17. [Security & integrity](#security--integrity)\n18. [Support](#support)\n\n---\n\n## Requirements\n\nBefore you wire the SDK in, make sure you have:\n\n- A **partner account** on the Othento platform with at least one **workflow**\n  configured. Workflow IDs are issued from your dashboard.\n- A **public API key** (`pk_sandbox_…` or `pk_live_…`) — sandbox keys hit the\n  test environment, live keys bill against your plan. **You do not pass an\n  environment flag**; the key decides. See [Sandbox vs production](#sandbox-vs-production).\n- An HTTPS hosting page (the camera API is gated to secure contexts).\n- A user-facing camera.\n\nIf you don't have credentials yet, contact your account manager to be\nonboarded.\n\n---\n\n## Install\n\n```bash\nnpm install @alialjundi/web-sdk\n# or\npnpm add @alialjundi/web-sdk\n# or\nyarn add @alialjundi/web-sdk\n```\n\nThe package ships ESM (`.mjs`), CommonJS (`.cjs`), and TypeScript\ndeclarations (`.d.ts`). Tree-shakers honour `sideEffects: false`.\n\n```ts\nimport { OthentoSDK, OthentoError } from '@alialjundi/web-sdk';\nimport type { OthentoConfig, OthentoDecision } from '@alialjundi/web-sdk';\n```\n\n---\n\n## Quick start\n\n```ts\nimport { OthentoSDK } from '@alialjundi/web-sdk';\n\nconst sdk = new OthentoSDK({\n  mode: 'create',\n  apiKey: '<<YOUR_PUBLIC_API_KEY>>',\n  workflowExternalId: '<<YOUR_WORKFLOW_ID>>',\n  clientData: 'user-123', // your end-user identifier\n\n  onReady: () => console.log('SDK ready'),\n  onSessionCreated: ({ externalId }) => trackSession(externalId),\n  onStatusChange: (status) => console.log('status:', status),\n  onCompleted: ({ decision, externalId }) =>\n    finishOnboarding(decision, externalId),\n  onCancelled: ({ reason }) => analytics.track('verify_cancel', { reason }),\n  onError: (err, displayMessage) => {\n    console.error(err.code, displayMessage);\n  },\n});\n\nsdk.start();\n```\n\nConfiguration is validated **synchronously**. Missing or malformed fields\nthrow `OthentoError` immediately — they do not surface via `onError`.\n\n---\n\n## Configuration reference\n\n`new OthentoSDK(config)` takes one argument: an `OthentoConfig` object.\n\n### Authentication\n\n| Field                  | Type            | Required | Notes                                                            |\n| ---------------------- | --------------- | -------- | ---------------------------------------------------------------- |\n| `mode`                 | `'create'`      | always   | Always `'create'`.                                               |\n| `apiKey`               | `string`        | always   | Public API key issued by your dashboard.                         |\n| `workflowExternalId`   | `string`        | always   | Verification workflow identifier.                                |\n| `clientData`           | `string`        | always   | Your end-user identifier; echoed in session events and webhooks. |\n\n### Session metadata\n\n| Field                | Type                                     | Required | Notes                                                                 |\n| -------------------- | ---------------------------------------- | -------- | --------------------------------------------------------------------- |\n| `callbackUrl`        | `string`                                 | optional | Forwarded to create-session. Where the user is redirected on completion (if applicable). |\n| `callbackReceiver`   | `'Initiator' \\| 'Completer' \\| 'Both'`   | optional | Which party receives the `callbackUrl` redirect.                      |\n| `metadata`           | `string`                                 | optional | Free-form string round-tripped on session events and webhooks.        |\n| `expectedDetails`    | `OthentoExpectedDetails`                    | optional | Pre-filled identity hints to compare against extracted data.          |\n\n### Presentation\n\n| Field                  | Type                       | Required | Default      | Notes                                                                 |\n| ---------------------- | -------------------------- | -------- | ------------ | --------------------------------------------------------------------- |\n| `container`            | `HTMLElement \\| string`    | optional | (modal)      | If set, renders inline inside the element (or CSS selector match). See [Presentation](#presentation-modal-vs-inline). |\n| `showCloseButton`      | `boolean`                  | optional | `true`       | Modal only. Hide the built-in × button if your UI provides its own dismissal. |\n| `closeOnBackdropClick` | `boolean`                  | optional | `false`      | Modal only. Treat backdrop clicks as cancellations.                   |\n\n### Network & timeouts\n\nThe SDK guards startup with a two-stage timeout: the iframe must fire its\n`load` event within `loadTimeoutMs`, then complete the ready handshake within\n`initTimeoutMs` (measured from `load`). Both are validated as positive numbers\nat construction. Raise them when your end-users are on slow or unreliable\nnetworks where the first cold load of the verification UI can exceed the\ndefaults.\n\n| Field           | Type     | Required | Default       | Notes                                                                              |\n| --------------- | -------- | -------- | ------------- | ---------------------------------------------------------------------------------- |\n| `loadTimeoutMs` | `number` | optional | `20000` (20s) | Time allowed for the iframe `load` event before `onError({ code: 'iframe_load_failed' })`. |\n| `initTimeoutMs` | `number` | optional | `15000` (15s) | Time allowed (after load) for the ready handshake before `onError({ code: 'init_timeout' })`. |\n\n```ts\n// Example: generous limits for users on weak mobile connections.\nconst sdk = new OthentoSDK({\n  mode: 'create',\n  apiKey: 'pk_live_…',\n  workflowExternalId: 'wf_…',\n  clientData: 'user-123',\n  loadTimeoutMs: 60000, // wait up to 60s for the iframe to load\n  initTimeoutMs: 30000, // then up to 30s for the handshake\n});\n```\n\n### `OthentoExpectedDetails`\n\nAll fields are optional. Send only what you have.\n\n```ts\ninterface OthentoExpectedDetails {\n  firstName?: string;\n  lastName?: string;\n  dateOfBirth?: string;   // ISO-8601, e.g. \"1990-04-23\"\n  gender?: string;\n  nationality?: string;\n  country?: string;\n  address?: string;\n  documentNumber?: string;\n  ipAddress?: string;\n}\n```\n\n### Sandbox vs production\n\nThere is **no environment flag**. Your API key (and the session minted from\nit) decides whether traffic hits sandbox or production. Use a sandbox key\nduring integration; swap to a live key when you're ready to bill real\nverifications. Same SDK build, same iframe URL, same bridge protocol.\n\n---\n\n## Lifecycle & callbacks\n\nA single session runs through this lifecycle:\n\n```\nconstructor → start() → onReady → onSessionCreated   ─┐\n                            ↓                         │\n                     onStatusChange (deduped, n×)     │\n                            ↓                         │\n                  exactly one terminal callback:      │\n                  onCompleted | onCancelled | onError │\n                            ↓                         │\n                     iframe unmounted, listeners cleared\n```\n\nCallbacks are invoked on a microtask, so they will not run synchronously\ninside `start()`. Exceptions thrown from your callback are caught and\nlogged — they do not crash the SDK.\n\n| Callback           | Signature                                                          | When it fires                                                                                       |\n| ------------------ | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |\n| `onReady`          | `() => void`                                                       | Iframe finished loading and completed the handshake. Fires once, before any other lifecycle event.  |\n| `onSessionCreated` | `(info: { externalId, sessionUrl }) => void`                       | A new session has been minted. Save `externalId` against your user record.    |\n| `onStatusChange`   | `(status: OthentoSessionStatus) => void`                              | Session status transitioned. Deduplicated — you never see the same status twice in a row.           |\n| `onCompleted`      | `(result: { decision, externalId }) => void`                       | Verification reached a terminal decision. `decision` is `Approved`, `Declined`, or `InReview`.      |\n| `onCancelled`      | `(info: { reason: OthentoCancelReason }) => void`                     | User dismissed the flow, host called `destroy()`, page unloaded, or the iframe self-cancelled.      |\n| `onError`          | `(error: OthentoError, displayMessage: string) => void`               | Any non-decision terminal error. `error.code` is a typed `OthentoErrorCode`.                           |\n\n**Exactly one** of `onCompleted` / `onCancelled` / `onError` fires per\nsession — they are mutually exclusive terminal events. After a terminal\nevent the SDK is in `destroyed` state; further calls to `destroy()` are\nsafe no-ops.\n\n---\n\n## Status & decision values\n\n```ts\ntype OthentoSessionStatus =\n  | 'Created'\n  | 'InProgress'\n  | 'Processing'\n  | 'Completed'\n  | 'Expired'\n  | 'Failed';\n\ntype OthentoDecision = 'Approved' | 'Declined' | 'InReview';\n```\n\nThese strings are identical to the Android SDK's enum names, so the same\nanalytics events and webhook payloads work across both platforms.\n\n---\n\n## Error codes\n\n`OthentoError.code` is a typed `OthentoErrorCode` union — switch on it for safe,\nexhaustive error handling. Code names match the Android SDK 1:1, plus\nweb-only additions for iframe and CSP failures.\n\n| `code`                       | Origin           | Meaning                                                       | Recommended action                                                                                       |\n| ---------------------------- | ---------------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |\n| `config_invalid`             | Thrown sync      | Required field missing or malformed.                          | Fix at build time — this should never reach production.                                                  |\n| `missing_api_key`            | Thrown sync      | Missing `apiKey`.                                             | Provide the public API key from your dashboard.                                                          |\n| `missing_workflow`           | Thrown sync      | Missing `workflowExternalId`.                                 | Provide the workflow ID configured in your dashboard.                                                    |\n| `missing_client_data`        | Thrown sync      | Missing `clientData`.                                         | Pass your end-user identifier.                                                                           |\n| `iframe_load_failed`         | `onError`        | Iframe never loaded within `loadTimeoutMs` (default 20 s).    | Check network connectivity and that your CSP allows `frame-src https://sdk-dev.zetatech.ae`. For slow networks, raise `loadTimeoutMs`. |\n| `init_timeout`               | `onError`        | Iframe loaded but never completed the ready handshake within `initTimeoutMs` (default 15 s). | Likely a CSP `frame-src` or mixed-content block, or the host page itself runs inside a script-restricted sandbox. On slow networks, raise `initTimeoutMs`. See [Troubleshooting](#troubleshooting). |\n| `version_mismatch`           | `onError`        | Bridge protocol version mismatch.                             | Upgrade the SDK to a version compatible with the iframe deployment.                                      |\n| `camera_permission_denied`   | `onError`        | User blocked the camera in the browser.                       | Show a \"grant camera and try again\" prompt. **This is the single largest drop-off cause in production.** |\n| `session_expired`            | `onError`        | The session was minted too long ago to start verification.    | Mint a new session and call `start()` on a fresh `OthentoSDK` instance.                                     |\n| `network`                    | `onError`        | Transient network failure inside the verification flow.       | Retry with the same session.                                                                             |\n| `create_failed`              | `onError`        | The session could not be created.                             | Verify `apiKey` and `workflowExternalId`; check the Othento status page.                                    |\n| `initiate_failed`            | `onError`        | The flow failed to start after the session was created.       | Retry on a fresh instance; if it persists, contact support.                                              |\n| `documents_failed`           | `onError`        | Document capture or processing failed inside the flow.        | Prompt the user to retry capture with better lighting and framing.                                       |\n| `upload_failed`              | `onError`        | Evidence upload failed. `error.stage` says which step.        | Retry; check connectivity. See [upload stages](#upload-stages) below.                                    |\n| `file_too_large`             | `onError`        | A captured file exceeded the upload size limit.               | Prompt the user to retry capture.                                                                        |\n| `poll_failed`                | `onError`        | Polling for the verification result failed.                   | Retry; the session may still resolve server-side.                                                        |\n| `session_not_found`          | `onError`        | The session id did not resolve to a live session.             | Mint a fresh session and start a new instance.                                                           |\n| `session_failed`             | `onError`        | The session ended in a failed state server-side.              | Mint a new session; inspect the session in your dashboard.                                               |\n| `cancelled`                  | `onError`        | The flow was aborted in a way the iframe reports as an error. | Treat as a cancellation and offer a retry.                                                               |\n| `unknown`                    | `onError`        | An unclassified runtime error.                                | Show `displayMessage`; capture the console log and `externalId` for support.                             |\n\n```ts\nonError: (err, displayMessage) => {\n  switch (err.code) {\n    case 'camera_permission_denied':\n      showCameraHelp();\n      break;\n    case 'session_expired':\n      restartSession();\n      break;\n    case 'iframe_load_failed':\n    case 'init_timeout':\n      showRetryWithSupportLink();\n      break;\n    case 'network':\n      showRetry();\n      break;\n    default:\n      showGenericError(displayMessage);\n  }\n};\n```\n\n`OthentoError` extends `Error` and carries `code`, `message`, and (where\nrelevant) `httpStatus` and `stage` fields.\n\n#### Upload stages\n\nWhen `error.code === 'upload_failed'`, `error.stage` is an `OthentoUploadStage`\npinpointing which step of the evidence upload failed:\n\n```ts\ntype OthentoUploadStage = 'UploadUrl' | 'S3Put' | 'ConfirmUpload';\n```\n\n| `stage`         | Failed step                                            |\n| --------------- | ------------------------------------------------------ |\n| `UploadUrl`     | Requesting the pre-signed upload URL.                  |\n| `S3Put`         | Uploading the file to object storage.                  |\n| `ConfirmUpload` | Confirming the completed upload with the backend.      |\n\n---\n\n## Presentation: modal vs inline\n\n### Modal (default)\n\nThe SDK renders as a centered modal over a backdrop and owns the chrome. The\nhost page's `<body>` gains an `overflow: hidden` lock for the duration of\nthe flow.\n\n### Inline\n\nPass a `container` to embed the SDK as a section of your own page. The host\nowns layout: no backdrop, no centering, no built-in close button.\n\n```ts\nnew OthentoSDK({\n  mode: 'create',\n  apiKey: '<<YOUR_PUBLIC_API_KEY>>',\n  workflowExternalId: '<<YOUR_WORKFLOW_ID>>',\n  clientData: 'user-123',\n  container: document.getElementById('verify-area')!, // or '#verify-area'\n  onCompleted: ({ decision }) => console.log(decision),\n}).start();\n```\n\nIn inline mode, `showCloseButton` and `closeOnBackdropClick` are ignored.\nSize the container yourself — the iframe defaults to\n`width: 100%; height: 100%; min-height: 560px` to avoid collapsing inside a\nzero-height parent.\n\nThe `container` is resolved at `start()` time, not at construction, so it is\nsafe to construct the SDK before the host element exists.\n\n---\n\n## Cancellation reasons\n\n`onCancelled({ reason })` fires once with a typed reason. Useful for\nanalytics funnels and adaptive retry UX.\n\n| `reason`        | Trigger                                                                              |\n| --------------- | ------------------------------------------------------------------------------------ |\n| `close-button`  | User clicked the built-in × button. Hide it with `showCloseButton: false`.           |\n| `backdrop`      | User clicked the backdrop. Off by default; enable with `closeOnBackdropClick: true`. |\n| `esc`           | User pressed Escape (modal mode only).                                               |\n| `page-hide`     | Host page was unloaded (`pagehide` event).                                           |\n| `host-destroy`  | Host called `sdk.destroy()` before a terminal event.                                 |\n| `iframe`        | Verification was cancelled from inside the iframe.                                   |\n\n```ts\nonCancelled: ({ reason }) => {\n  analytics.track('othento_cancel', { reason });\n  if (reason === 'iframe') showRetryPrompt();\n};\n```\n\n---\n\n## Cleaning up in SPAs\n\nIf you embed the SDK inside an Angular/React/Vue route, call `sdk.destroy()`\non route change / component unmount. Without it the iframe, postMessage\nlisteners, and (in modal mode) the document body `overflow` lock persist\nafter navigation.\n\n### Angular\n\n```ts\nexport class VerifyComponent implements OnDestroy {\n  private sdk = new OthentoSDK({ /* … */ });\n\n  ngOnInit() { this.sdk.start(); }\n  ngOnDestroy() { this.sdk.destroy(); }\n}\n```\n\n### React\n\n```tsx\nuseEffect(() => {\n  const sdk = new OthentoSDK({ /* … */ });\n  sdk.start();\n  return () => sdk.destroy();\n}, []);\n```\n\n### Vue\n\n```ts\nconst sdk = new OthentoSDK({ /* … */ });\nonMounted(() => sdk.start());\nonBeforeUnmount(() => sdk.destroy());\n```\n\nAfter a terminal callback (`onCompleted` / `onCancelled` / `onError`),\n`destroy()` is a safe no-op.\n\n### Multi-instance safety\n\nConstructing a second `OthentoSDK` while one is already running throws\nsynchronously with `code: 'config_invalid'`. Destroy the first instance\nbefore starting another.\n\n---\n\n## Content Security Policy & permissions\n\nThe hosting page must allow the SDK iframe and grant it camera access.\n\n```\n# Content-Security-Policy\nframe-src https://sdk-dev.zetatech.ae;\n```\n\n```\n# Permissions-Policy (if you set one)\ncamera=(self \"https://sdk-dev.zetatech.ae\")\n```\n\nThe SDK mounts the iframe with\n`allow=\"camera\"`. The bridge enforces origin verification on\nboth incoming and outgoing `postMessage` payloads.\n\nIf your CSP omits `frame-src`, the iframe will silently fail to load and the\nSDK fires `onError({ code: 'iframe_load_failed' })` after `loadTimeoutMs`\n(20 seconds by default).\n\n---\n\n## Testing your integration\n\nA working integration should produce:\n\n1. `onReady` fires within ~1 s of `start()` on a healthy network.\n2. `onSessionCreated` fires next, with a non-empty `externalId`.\n3. `onStatusChange` fires at least once with `'InProgress'`.\n4. Exactly one terminal callback fires: `onCompleted`, `onCancelled`, or\n   `onError`.\n\nIn sandbox, use the test identity documents listed in the dashboard to\nexercise approved / declined / in-review paths deterministically.\n\n### Sandbox flow checklist\n\n- [ ] Sandbox API key in place; the iframe loads\n- [ ] `onReady` and `onSessionCreated` fire\n- [ ] Approved-path test document → `onCompleted({ decision: 'Approved' })`\n- [ ] Declined-path test document → `onCompleted({ decision: 'Declined' })`\n- [ ] In-review test document → `onCompleted({ decision: 'InReview' })`\n- [ ] Closing the modal mid-flow → `onCancelled({ reason: 'close-button' })`\n\n---\n\n## Troubleshooting\n\n| Symptom                                                          | Likely cause                                                                                  | Fix                                                                                              |\n| ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |\n| `onError({ code: 'iframe_load_failed' })` after `loadTimeoutMs` (~20 s default) | CSP `frame-src` does not allow `https://sdk-dev.zetatech.ae`, network blocks the host, or the cold load is slower than the timeout. | Update CSP. Verify the iframe loads in DevTools → Network. On weak networks, raise `loadTimeoutMs`. |\n| `onError({ code: 'init_timeout' })` after `initTimeoutMs` (~15 s default) | Iframe loaded but the bridge handshake never completed. Often a CSP block on the SDK origin, the host page is embedded in a script-restricted sandbox, or a slow device/network. | Verify the SDK origin loads in DevTools → Network, and that the host page is not embedded in a sandboxed iframe without `allow-scripts`. On slow networks, raise `initTimeoutMs`. |\n| `onError({ code: 'camera_permission_denied' })`                  | User denied camera permission, or the host page is served over `http://`.                     | Serve over HTTPS. Show a recovery UI instructing the user to grant camera permission and retry.  |\n| Modal opens but stays blank                                      | Mixed content (HTTP host loading the HTTPS iframe), or `permissions-policy` blocks camera.    | Move host to HTTPS. Add `camera` to `Permissions-Policy`.                                        |\n| `onCancelled({ reason: 'page-hide' })` immediately after `start()` | The SPA route changed and unmounted the host component before the flow could open.          | Construct and `start()` the SDK only when the route component is stably mounted.                 |\n| Constructor throws `'Another OthentoSDK instance is currently active'` | A previous instance was not destroyed (e.g. component re-mounted in dev mode under StrictMode). | Call `sdk.destroy()` on unmount and guard against double construction.                          |\n| Callbacks fire in the wrong order                                | Almost never the SDK — callbacks are dispatched on a microtask. Check for synchronous throws inside them. | Wrap callbacks in try/catch and log; an exception inside one callback does not prevent the next. |\n\nIf the symptom is not listed here, capture the network HAR and the browser\nconsole and contact [support](#support).\n\n---\n\n## TypeScript\n\nThe package ships first-class TypeScript declarations with the build.\nNo `@types/…` install is needed.\n\n```ts\nimport {\n  OthentoSDK,\n  OthentoError,\n  type OthentoConfig,\n  type OthentoDecision,\n  type OthentoErrorCode,\n  type OthentoSessionStatus,\n  type OthentoCancelReason,\n  type OthentoExpectedDetails,\n  type OthentoCallbacks,\n} from '@alialjundi/web-sdk';\n```\n\n`OthentoErrorCode` is a union literal; `switch` over it for exhaustive\nhandling, and TypeScript will flag missing cases when new codes are added in\nfuture major versions.\n\n---\n\n## Browser support\n\nModern evergreen browsers:\n\n| Browser        | Minimum version |\n| -------------- | --------------- |\n| Chrome / Edge  | Latest 2 stable releases |\n| Firefox        | Latest 2 stable releases |\n| Safari (macOS) | 15+             |\n| Safari (iOS)   | 14+             |\n| Chrome (Android) | Android 7+    |\n\nCamera access requires a **secure context** (HTTPS or `localhost`). The SDK\nwill load on `http://` but the verification flow will fail immediately with\n`camera_permission_denied`.\n\n---\n\n## Versioning & changelog\n\nThis package follows [Semantic Versioning](https://semver.org/):\n\n- **Patch** (`x.y.Z`): bug fixes, internal refactors. Safe to auto-update.\n- **Minor** (`x.Y.0`): new optional config, new callbacks, new error codes.\n  Existing callbacks keep their signatures.\n- **Major** (`X.0.0`): breaking changes to public types or callback\n  signatures. Migration notes ship in [CHANGELOG.md](./CHANGELOG.md).\n\nPre-`1.0.0` releases (`0.x.y`) may make breaking changes in minor versions\nas the API stabilises. Pin an exact version in production until `1.0.0`.\n\n---\n\n## Security & integrity\n\n- The SDK loads its iframe from a **fixed origin** that ships in the bundle.\n  Consumers cannot redirect it to a different host.\n- The `postMessage` bridge **verifies the origin** on every inbound and\n  outbound message — messages from unknown origins are dropped.\n- The bridge is **versioned** (`v: 1`). Protocol mismatches surface as\n  `onError({ code: 'version_mismatch' })` rather than crashing or producing\n  undefined behaviour.\n- PII, document images, and biometric data **never cross the host page** —\n  they are captured and uploaded directly from the iframe to the Othento\n  backend.\n- Public API keys are scoped: they can only mint sessions and read their\n  own data.\n\n---\n\n## Support\n\n- **Documentation:** the latest version of this README and the full API\n  reference live alongside the package on the dashboard.\n- **Status & incidents:** check the Othento status page before opening a\n  ticket.\n- **Account & integration help:** reach out to your account manager or\n  open a ticket through the dashboard. Include the SDK version\n  (`package.json`), the `externalId` of the affected session, and the\n  browser console log.\n\n---\n\n## License\n\nUNLICENSED — use under your Othento partner agreement. See\n[CHANGELOG.md](./CHANGELOG.md) for release history.\n","readmeFilename":"README.md","_rev":"1-6f0eb1b08813b4c9d5f1879f5e5c7f1a"}