Skip to content

Secure handshake

GET https://dev.sahl-ai.pages.dev/iframe-secure

The recommended way to launch the panel. The page loads with no token in its URL. Once it is ready, it asks your page for credentials, and your page sends the token and encounter context with postMessage.

1. Get a token on your backend

Call POST /users/login with the signed-in clinician's email as identifier. Pass the returned token to the EHR page through your own authenticated channel.

2. Embed the panel

<iframe id="sahl-iframe"
        src="https://dev.sahl-ai.pages.dev/iframe-secure"
        allow="microphone; display-capture"></iframe>

If Sahl AI has set up an organization theme and response mapping for you, add it to the URL: /iframe-secure?theme=<your-theme>. theme is the only parameter that goes in the URL. Everything else goes in the handshake payload.

3. Answer the handshake

const SAHL_ORIGIN = "https://dev.sahl-ai.pages.dev";
const sahlFrame = document.getElementById("sahl-iframe");

window.addEventListener("message", (event) => {
  // 1. Accept messages only from the Sahl panel
  if (event.origin !== SAHL_ORIGIN) return;
  if (event.source !== sahlFrame.contentWindow) return;

  // 2. The panel is ready: send credentials and context
  if (event.data?.type === "SAHL_IFRAME_READY") {
    sahlFrame.contentWindow.postMessage(
      {
        type: "SAHL_IFRAME_INIT",
        payload: {
          access_token: ACCESS_TOKEN, // required
          px_id: "PATIENT_ID",
          ex_id: "ENCOUNTER_ID",
          ux_id: "USER_ID",
          age: 16436, // age in days
          gender: "female",
          cta: "Send to EHR"
        }
      },
      SAHL_ORIGIN // always target the Sahl origin, never "*"
    );
    return;
  }

  // 3. Results and lifecycle events
  handleSahlData(event.data);
});

The payload fields are described in Launch parameters.

Handshake contract

Message Direction Shape
SAHL_IFRAME_READY Panel → your page { type: "SAHL_IFRAME_READY" }
SAHL_IFRAME_INIT Your page → panel { type: "SAHL_IFRAME_INIT", payload: { access_token, ... } }

Rules:

  • Reply within 10 seconds of SAHL_IFRAME_READY. After that the panel shows "Timed out waiting for authentication from the parent window."
  • access_token must be a non-empty string. If it is missing or payload is not an object, the panel shows an error and stops.
  • The panel accepts the first valid SAHL_IFRAME_INIT and ignores the rest. To relaunch, for example for another patient, reset the iframe src and answer the new READY.
  • The panel posts results only to the origin that completed the handshake. Your listener must be on the same page that sent SAHL_IFRAME_INIT.

Security checklist

  • Login runs on your backend; credentials never reach the browser.
  • Your listener checks event.origin and event.source.
  • postMessage targets https://dev.sahl-ai.pages.dev, never "*".
  • Tokens are not logged on either side.