The PostMessage API enables secure cross-origin communication between your application and the embedded Corti Assistant. This method is recommended for iframe or WebView integrations.
This method is recommended for iFrame/WebView integration. Note, a web component is coming soon that will make this integration easier to use.
Overview
The PostMessage API uses the browser’s postMessage mechanism to enable secure communication between your application and the embedded Corti Assistant, even when they’re served from different origins. This makes it ideal for embedding Corti Assistant in iframes or WebViews.
Requirements
Before getting started, ensure you have:
Access to Corti Assistant : You’ll need credentials and access to one of the available regions
HTTPS : The embedded Assistant must be loaded over HTTPS (required for microphone access)
Microphone permissions : Your application must request and handle microphone permissions appropriately
OAuth2 client : You’ll need an OAuth2 client configured for user-based authentication
Modern browser or WebView : For web applications, use a modern browser. For native apps, use a modern WebView
Recommendations
Validate message origins to ensure security
Use specific target origins instead of '*' when possible
Implement proper error handling for all API calls
Handle authentication token refresh to maintain user sessions
Request microphone permissions before initializing the embedded Assistant
Available Regions
Features
Secure cross-origin communication
Works with any iframe or WebView implementation
Fully asynchronous with request/response pattern
Quick Start
Step 1: Set Up Authentication
The Embedded Assistant API only supports user-based authentication . You must authenticate as an end user, not as an application. Client credentials and other machine-to-machine authentication methods are not supported.
Before you can use the PostMessage API, you need to authenticate your users using OAuth2. The recommended flow is Authorization Code Flow with PKCE for secure, user-facing integrations.
For detailed information on OAuth2 flows and authentication, see our OAuth Authentication Guide .
Key points:
Use Authorization Code Flow with PKCE for embedded integrations
Obtain access_token and refresh_token for your users
Handle token refresh to maintain sessions
Never expose client secrets in client-side code
Step 2: Load the Embedded Assistant
Load the Corti Assistant in an iframe or WebView:
EU example
EU MD example
US example
<!-- Load the embedded Corti iframe -->
< iframe id = "corti-iframe" src = "https://assistant.eu.corti.app/embedded" width = "100%" height = "600px" ></ iframe >
< script >
const iframe = document.getElementById('corti-iframe');
let isReady = false;
// Listen for the ready event
window.addEventListener('message', async (event) => {
if ( event . data ?. type === 'CORTI_EMBEDDED_EVENT' && event . data . event === 'ready' ) {
isReady = true ;
console.log( 'Corti embedded app is ready' );
// Authenticate the user (requires OAuth2 tokens)
await authenticateUser();
}
});
async function authenticateUser() {
// Send authentication request with OAuth2 tokens
iframe.contentWindow.postMessage({
type: 'CORTI_EMBEDDED' ,
version: 'v1' ,
action: 'auth' ,
requestId: 'auth-1' ,
payload: {
mode: 'stateful' ,
access_token: 'your-access-token' , // From OAuth2 flow
refresh_token: 'your-refresh-token' // From OAuth2 flow
}
}, '*' );
}
</ script >
See all 33 lines
All messages sent to the embedded app follow this structure:
{
type : 'CORTI_EMBEDDED' ,
version : 'v1' ,
action : string ,
requestId ?: string ,
payload ?: object
}
Message Properties
type: Always 'CORTI_EMBEDDED'
version: API version (currently 'v1')
action: The action to perform (see API Reference for all actions)
requestId: Optional unique identifier for tracking responses
payload: Optional data specific to the action
Response Handling
Responses from the embedded app are sent via postMessage and can be identified by checking the message type:
window . addEventListener ( 'message' , ( event ) => {
// Handle responses
if ( event . data ?. type === 'CORTI_EMBEDDED_RESPONSE' ) {
const { requestId , success , data , error } = event . data ;
// Handle response
}
// Handle events
if ( event . data ?. type === 'CORTI_EMBEDDED_EVENT' ) {
const { event : eventType , payload } = event . data ;
// Handle event
}
});
Complete Integration Example
Here’s a complete example showing the recommended integration flow with proper request/response handling:
Example Embedded Integration
// State management
let iframe = null ;
let isReady = false ;
let currentInteractionId = null ;
let pendingRequests = new Map ();
// Initialize the integration
function initializeCortiEmbeddedIntegration ( iframeElement ) {
iframe = iframeElement ;
isReady = false ;
currentInteractionId = null ;
setupEventListeners ();
}
function setupEventListeners () {
window . addEventListener ( 'message' , ( event ) => {
// Handle responses
if ( event . data ?. type === 'CORTI_EMBEDDED_RESPONSE' ) {
handleResponse ( event . data );
}
// Handle events
if ( event . data ?. type === 'CORTI_EMBEDDED_EVENT' ) {
handleEvent ( event . data );
}
});
}
function handleResponse ( responseData ) {
const { requestId , success , data , error } = responseData ;
const pendingRequest = pendingRequests . get ( requestId );
if ( pendingRequest ) {
pendingRequests . delete ( requestId );
if ( success ) {
pendingRequest . resolve ( data );
} else {
pendingRequest . reject ( new Error ( error ?. message || 'Request failed' ));
}
}
}
function handleEvent ( eventData ) {
switch ( eventData . event ) {
case 'ready' :
isReady = true ;
startIntegrationFlow ();
break ;
case 'documentGenerated' :
onDocumentGenerated ( eventData . payload . document );
break ;
case 'recordingStarted' :
console . log ( 'Recording started' );
break ;
case 'recordingStopped' :
console . log ( 'Recording stopped' );
break ;
// ... handle other events
}
}
function generateRequestId () {
return `req- ${ Date . now () } - ${ Math . random (). toString ( 36 ). substr ( 2 , 9 ) } ` ;
}
async function startIntegrationFlow () {
try {
// 1. Authenticate
await authenticate ();
// 2. Configure session
await configureSession ();
// 3. Create interaction
const interaction = await createInteraction ();
// 4. Add relevant facts
await addFacts ();
// 5. Navigate to interaction UI
await navigateToSession ( interaction . id );
console . log ( 'Integration flow completed successfully' );
} catch ( error ) {
console . error ( 'Integration flow failed:' , error );
}
}
function sendMessage ( action , payload = {}) {
return new Promise (( resolve , reject ) => {
const requestId = generateRequestId ();
pendingRequests . set ( requestId , { resolve , reject });
iframe . contentWindow . postMessage ({
type: 'CORTI_EMBEDDED' ,
version: 'v1' ,
action ,
requestId ,
payload
}, '*' );
// Optional: Add timeout
setTimeout (() => {
if ( pendingRequests . has ( requestId )) {
pendingRequests . delete ( requestId );
reject ( new Error ( 'Request timeout' ));
}
}, 30000 );
});
}
async function authenticate () {
// Requires OAuth2 tokens from user authentication
return sendMessage ( 'auth' , {
mode: 'stateful' ,
access_token: 'your-access-token' , // From OAuth2 flow
refresh_token: 'your-refresh-token' // From OAuth2 flow
});
}
async function configureSession () {
return sendMessage ( 'configureSession' , {
defaultLanguage: 'en' ,
defaultOutputLanguage: 'en' ,
defaultTemplateKey: 'corti-soap' ,
defaultMode: 'virtual'
});
}
async function createInteraction () {
return sendMessage ( 'createInteraction' , {
assignedUserId: null ,
encounter: {
identifier: `encounter- ${ Date . now () } ` ,
status: "planned" ,
type: "first_consultation" ,
period: {
startedAt: new Date (). toISOString (),
},
title: "Initial Consultation" ,
},
});
}
async function addFacts () {
return sendMessage ( 'addFacts' , {
facts: [
{ text: "Chest pain" , group: "other" },
{ text: "Shortness of breath" , group: "other" },
]
});
}
async function navigateToSession ( interactionId ) {
return sendMessage ( 'navigate' , {
path: `/session/ ${ interactionId } `
});
}
function onDocumentGenerated ( document ) {
console . log ( 'Document generated:' , document );
}
// Usage example:
const iframeElement = document . getElementById ( 'corti-iframe' );
initializeCortiEmbeddedIntegration ( iframeElement );
See all 167 lines
Error Handling
Always handle errors when making requests:
try {
const result = await sendMessage ( 'auth' , {
mode: 'stateful' ,
accessToken: 'your-access-token' ,
refreshToken: 'your-refresh-token'
});
console . log ( 'Authentication successful:' , result );
} catch ( error ) {
console . error ( 'Authentication failed:' , error . message );
// Handle authentication failure
}
See all 11 lines
Security Considerations
When using postMessage, always:
Validate message origin : Check event.origin to ensure messages come from trusted sources
Use specific target origins : Replace '*' with the specific origin when possible
Sanitize data : Never trust data from postMessage without validation
const ALLOWED_ORIGINS = [
'https://assistant.eu.corti.app' ,
'https://assistantmd.eu.corti.app' ,
'https://assistant.us.corti.app'
];
window . addEventListener ( 'message' , ( event ) => {
// Validate origin
if ( ! ALLOWED_ORIGINS . includes ( event . origin )) {
console . warn ( 'Message from untrusted origin:' , event . origin );
return ;
}
// Process message
if ( event . data ?. type === 'CORTI_EMBEDDED_EVENT' ) {
// Handle event
}
});
// Send messages with specific origin
iframe . contentWindow . postMessage ({
type: 'CORTI_EMBEDDED' ,
version: 'v1' ,
action: 'auth' ,
payload: { /* ... */ }
}, 'https://assistant.eu.corti.app' ); // Specific origin instead of '*'
See all 26 lines
Next Steps