Natzar Partner API Reference - v1.0.0
    Preparing search index...

    Embedding the patient UI

    Two custom elements let you drop the full Natzar patient experience into your own pages instead of building chat/video UIs yourself:

    • <natzar-async> — the async-consult chat: consent prompt, message thread, attachments, rating sheet. The composer is live as soon as the patient consents — even while the consult is still queued waiting for a physician, the patient can send context (symptoms, photos) and it persists to the transcript as the physician's intake.
    • <natzar-telehealth> — the live video consult: waiting room, camera check, the call itself, rating.

    Two lines per widget. Load the embed script once, then render the tag with a session token your server minted:

    <script src="https://us.app.natzar.ai/embed/v1/embed.js"></script>
    <natzar-telehealth session-token="…" locale="en" theme="auto"></natzar-telehealth>
    <script src="https://us.app.natzar.ai/embed/v1/embed.js"></script>
    <natzar-async session-token="…" locale="en" theme="auto"></natzar-async>

    The script and the widget iframe are served by the Natzar app origin for the environment your API key belongs to. Load the script from the origin matching the key that minted the token — a pp_test_… token will not open in the production widget.

    Environment Embed origin
    prod https://us.app.natzar.ai
    stage https://stage.us.app.natzar.ai
    dev https://dev.us.app.natzar.ai

    @natzar/client carries the same table, so you can build the URL instead of hardcoding it:

    import {resolveEndpoints} from '@natzar/client';

    const {embedOrigin} = resolveEndpoints({environment: 'prod'});
    const src = `${embedOrigin}/embed/v1/embed.js`;

    Getting the token (server-side, with your API key — never in the browser):

    • Telehealth: POST /v1/telehealth-consults/{id}/embed-session
    • Async: POST /v1/async-consults/{id}/embed-session, or take the sessionToken returned directly by POST /v1/async-consults when you create the consult with consent: "embed".

    The response carries {sessionToken, expiresAt}. Tokens are consult-scoped (they open exactly one consult) and short-lived (1 hour by default; ttlSeconds 60–86400 on the mint request). The token's kind must match the tag — a telehealth token in <natzar-async> refuses to load.

    • HTTPS only. The widgets run in an iframe on a secure origin, and the telehealth widget needs getUserMedia — browsers only grant camera and microphone access in a secure context. A plain-HTTP host page cannot run the video widget (localhost is exempt for development).
    • Your page's origin must be allowlisted. The widget verifies the embedding page's origin against your partner account's allowedOrigins list and refuses to load from anywhere else (origin_not_allowed, surfaced as a natzar:error event). Register every origin you embed from — scheme + host + port, e.g. https://portal.your-clinic.com. The check covers the widget's ancestor origins; note that tokens being consult-scoped and short-lived is the primary containment — treat the allowlist as defense in depth, not as a substitute for keeping tokens out of untrusted pages.

    The embed script sets allow="camera; microphone; display-capture" on the iframe it creates, so no host-page changes are usually needed — the browser prompts the patient inside your page and the stream stays in the iframe.

    The exception: if your embedding page is itself inside an iframe (your page is nested in yet another product), every ancestor frame must also delegate camera/microphone via its own allow attribute — permissions policy delegation is per-hop. Same if your site sends a restrictive Permissions-Policy response header: it must not deny camera / microphone for the embed to work.

    All attributes are observed — changing one re-renders the widget (changing session-token is equivalent to calling refresh).

    Attribute Required Values Meaning
    session-token yes JWT from /embed-session Which consult the widget shows. Mint server-side only.
    locale no en es de fr it UI language. Default: browser language, falling back to en.
    theme no light dark auto Color scheme. Default auto (follows prefers-color-scheme).
    Member Meaning
    refresh(sessionToken) Swap in a freshly minted token without tearing down the widget — the intended reaction to natzar:expired. In-flight calls and a live video call continue uninterrupted.

    The widget dispatches DOM CustomEvents on the element, named natzar:<event>; the payload is in event.detail (shapes: EmbedEventDetailMap).

    const el = document.querySelector('natzar-async');
    el.addEventListener('natzar:message', (e) => console.log(e.detail.message));
    el.addEventListener('natzar:expired', async () => el.refresh(await mintToken()));
    Event Surface detail When
    natzar:ready both {type, consultId} Widget loaded, verified its token, rendered.
    natzar:consented async {consultId} Patient accepted the consent prompt.
    natzar:queued both {consultId} Waiting for a physician (async: no capacity yet — the chat still accepts messages, which persist for the physician; telehealth: waiting room).
    natzar:assigned both {consultId, physicianName?} A physician took the consult.
    natzar:message async {consultId, message} A new message appeared on the thread (any author) — useful for host-page unread badges.
    natzar:joined telehealth {consultId} The patient entered the live call.
    natzar:ended telehealth {consultId} The call ended (rating step may follow).
    natzar:rated both {consultId, stars?} The patient submitted a rating.
    natzar:closed both {consultId, closedReason?} Terminal state (a declined consent surfaces here with closedReason: 'declined').
    natzar:expired both {consultId} The session token expired; the widget freezes read-only until you refresh(token).
    natzar:error both {code?, message} Something went wrong (invalid token, disallowed origin, network…).

    Plan for sessions longer than one token lifetime (a patient can sit in a chat or waiting room for a while):

    el.addEventListener('natzar:expired', async ({detail}) => {
    // Your endpoint calls POST /v1/…/{consultId}/embed-session with YOUR key.
    const {sessionToken} = await fetch(`/api/natzar-token?consult=${detail.consultId}`)
    .then((r) => r.json());
    el.refresh(sessionToken);
    });

    Rotating your API key coarsely invalidates outstanding embed tokens (see Authentication) — the same natzar:expired / refresh path covers that too.

    If you'd rather manage the iframe yourself, the underlying protocol is window.postMessage: the iframe posts envelopes {source: 'natzar-embed', event, data} ( EmbedPostMessage) to its parent. Always check source === 'natzar-embed' AND the message origin before trusting one. The custom elements are exactly this plumbing plus origin checks and the natzar:-prefixed re-dispatch — prefer them.