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_tokenmust be a non-empty string. If it is missing orpayloadis not an object, the panel shows an error and stops.- The panel accepts the first valid
SAHL_IFRAME_INITand ignores the rest. To relaunch, for example for another patient, reset the iframesrcand answer the newREADY. - 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.originandevent.source. -
postMessagetargetshttps://dev.sahl-ai.pages.dev, never"*". - Tokens are not logged on either side.