kyve.dev

Iframe integration

Embedding the verification UI and handling postMessage events.

The iframe is a self-contained verification UI hosted on the platform domain. Your frontend embeds it and listens to postMessage events for real-time updates.

Embedding

<iframe
  src="https://iframe.kyve.dev?session_id=ses_...&token=..."
  allow="camera; microphone"
  sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
  style="width: 100%; min-height: 560px; border: 0"
></iframe>

The session_id and token come from POST /v1/sessions. Both are required — the iframe will refuse to initialize without them.

Event envelope

Every postMessage event uses a fixed envelope:

interface IframeEventEnvelope {
  type: string;           // e.g. "kyc.completed"
  version: 1;             // protocol version
  session_id: string;
  livemode: boolean;
  emitted_at: string;     // ISO 8601
  data: unknown;          // type-specific payload
}

Event types

TypeWhen it fires
kyc.readyIframe has initialized and validated the SDK token.
kyc.status_changedA step advances (document_uploadliveness, etc.).
kyc.resizeIframe content height changed — adjust your host element.
kyc.completedTerminal success. The verification is saved.
kyc.errorTerminal failure with a machine-readable code.
kyc.canceledUser or parent aborted the flow.

Always validate event.origin

Only accept messages whose origin matches your configured iframe origin. Drop everything else.

window.addEventListener('message', (event) => {
  if (event.origin !== 'https://iframe.kyve.dev') return;
  const envelope = event.data as IframeEventEnvelope;
  if (envelope?.version !== 1) return;
  // ...handle event
});

Parent → iframe commands

You can send commands to the iframe from the parent window:

  • kyc.configure — set theme (light / dark) and locale.
  • kyc.cancel — cancel the current flow cleanly.

Session resume after a page refresh is automatic — the iframe fetches the current snapshot on initialization and renders the correct screen. There is no explicit resume command.

All commands use the same envelope shape as iframe → parent events.

Image uploads (under the hood)

You don't see this from the parent — it's worth knowing it exists.

Before any document or selfie body is sent to /v1/iframe/upload, the iframe normalizes the image in the browser:

  • EXIF orientation is baked into pixels via createImageBitmap(file, { imageOrientation: 'from-image' }). Several downstream face providers (AWS Rekognition included) do not auto-rotate based on the EXIF tag, so a portrait iPhone capture would otherwise arrive sideways and fail face detection.
  • Resized to 2048px on the longest side. Raw mobile captures are often 4032×3024 and >3MB — downscaling shaves bandwidth and keeps face detection focused on the subject rather than searching a 12MP frame.
  • Re-encoded as JPEG at quality 0.92. This also strips all EXIF data (no GPS coordinates, device identifiers, or capture timestamps leave the browser).
  • Fail-open. If the browser cannot decode the file or the canvas context is unavailable, the original bytes are uploaded as-is so the flow keeps working.

This is invisible to the parent integration and requires no configuration on your end.

Mobile camera handoff

Desktop sessions can hand the applicant off to a mobile device for the capture steps. The desktop iframe surfaces a QR code linking to a hosted mobile page; the mobile page consumes a one-time capability token (hof_...) via POST /v1/hosted/claim and is issued a short-lived mobile SDK token scoped to the same session_id. The desktop iframe polls for state and renders progress in real time. No parent action is needed — the events stream through the existing postMessage channel as if the desktop did the capture.

On this page