> ## Documentation Index
> Fetch the complete documentation index at: https://docs.corti.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# PostMessage API

> Use the PostMessage API to integrate Corti Assistant via iframe or WebView

The PostMessage API enables secure cross-origin communication between your application and the embedded Corti Assistant. This method is suitable for iframe or WebView integrations requiring fine-grained control over the iframe lifecycle.

<Note>
  **Web Component API is recommended.** The [Web Component API](/assistant/web-component-api) provides the same functionality with a simpler interface. PostMessage API remains fully supported but involves unnecessary complexity. It is not deprecated.
</Note>

<Info>
  **Web Component has full working examples.** Complete, runnable examples are available for the [Web Component integration method](https://github.com/corticph/corti-examples/tree/main/embedded-assistant), which is the recommended approach for most integrations. PostMessage API examples demonstrating the lower-level protocol will be added to the repository in the future.
</Info>

## 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

* **US**: [https://assistant.us.corti.app](https://assistant.us.corti.app)
* **EU**: [https://assistant.eu.corti.app](https://assistant.eu.corti.app)
* **EU MD**: [https://assistantmd.eu.corti.app](https://assistantmd.eu.corti.app) (medical device compliant)

## 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

Before using the PostMessage API, authenticate your users using OAuth2. See the [Authentication Guide](/assistant/authentication) for complete setup instructions including Authorization Code Flow with PKCE (recommended), obtaining tokens, and handling token refresh.

<Info>
  All Embedded Assistant integrations require user-based OAuth2 authentication. Client credentials and machine-to-machine flows are not supported.
</Info>

* 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:

#### Required iframe `allow` permissions

When you embed Corti Assistant in an iframe, you must delegate the browser permissions that the embedded app needs.

* `microphone` is required for recording and dictation
* `display-capture` is required if you use virtual recording to capture audio from another tab, window, or screen
* `clipboard-write` is recommended so users can copy generated content reliably across browsers

Use the `allow` attribute on the iframe:

```html theme={null}
<iframe
  id="corti-iframe"
  src="https://assistant.eu.corti.app/embedded"
  width="100%"
  height="600px"
  allow="microphone; display-capture; clipboard-write"
></iframe>
```

<Note>
  These examples omit `*` intentionally. In an iframe `allow` attribute, each feature defaults to the iframe `src` origin, which is the safer default for Corti Assistant embeds. Use `feature *` only if you explicitly want to grant that permission to any origin the iframe might later navigate to.
</Note>

<Warning>
  If you omit `allow="microphone"` on the iframe, the embedded Assistant cannot access the user's microphone even if the site itself has microphone permission.
</Warning>

<Info>
  If you support virtual mode, include `display-capture` so the embedded Assistant can request browser-managed capture of remote audio streams.
</Info>

<CodeGroup>
  ```html European Region expandable theme={null}
  <!-- Load the embedded Corti iframe -->
  <iframe
    id="corti-iframe"
    src="https://assistant.eu.corti.app/embedded"
    width="100%"
    height="600px"
    allow="microphone; display-capture; clipboard-write"
  ></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 === 'embedded.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: {
        access_token: 'your-access-token', // From OAuth2 flow
        refresh_token: 'your-refresh-token', // From OAuth2 flow
        id_token: "your-id-token", // From OAuth2 flow
        token_type: "Bearer",
      }
    }, '*');
  }
  </script>
  ```

  ```html European Region (Medical Device) expandable theme={null}
  <!-- Load the embedded Corti iframe -->
  <iframe
    id="corti-iframe"
    src="https://assistantmd.eu.corti.app/embedded"
    width="100%"
    height="600px"
    allow="microphone; display-capture; clipboard-write"
  ></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 === 'embedded.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: {
        access_token: 'your-access-token', // From OAuth2 flow
        refresh_token: 'your-refresh-token', // From OAuth2 flow
        id_token: "your-id-token", // From OAuth2 flow
        token_type: "Bearer",
      }
    }, '*');
  }
  </script>
  ```

  ```html Americas Region expandable theme={null}
  <!-- Load the embedded Corti iframe -->
  <iframe
    id="corti-iframe"
    src="https://assistant.us.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 === "embedded.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: {
            access_token: "your-access-token", // From OAuth2 flow
            refresh_token: "your-refresh-token", // From OAuth2 flow
            id_token: "your-id-token", // From OAuth2 flow
            token_type: "Bearer",
          },
        },
        "*",
      );
    }
  </script>
  ```
</CodeGroup>

## Message format

All messages sent to the embedded app follow this structure:

```typescript theme={null}
{
  type: 'CORTI_EMBEDDED',
  version: 'v1',
  action: string,
  requestId?: string,
  payload?: object
}
```

### Message properties

* `type`: Always `'CORTI_EMBEDDED'`
* `version`: API version (currently `'v1'`)
* `action`: The method to invoke (see [API Reference](/assistant/api-reference) for all methods)
* `requestId`: Optional unique identifier for tracking responses
* `payload`: Optional data specific to the action

### Same API as Web Component

The PostMessage API provides access to the same methods documented in the [API Reference](/assistant/api-reference) - `auth`, `configure`, `createInteraction`, etc. - with identical parameters and behavior.

**Key difference:** Instead of direct method calls that return Promises, you communicate through messages:

1. **Send a message** with `action` field matching the method name
2. **Receive the response** asynchronously via a separate `CORTI_EMBEDDED_RESPONSE` message

Example for `auth`:

```javascript theme={null}
// Web Component: Direct call with Promise
const user = await api.auth({
  access_token,
  refresh_token,
  id_token,
  token_type,
});

// PostMessage: Send message, listen for response
iframe.contentWindow.postMessage(
  {
    type: "CORTI_EMBEDDED",
    version: "v1",
    action: "auth", // Method name goes here
    requestId: "auth-1",
    payload: { access_token, refresh_token, id_token, token_type },
  },
  "*",
);

// Later, in your message listener:
window.addEventListener("message", (event) => {
  if (
    event.data.type === "CORTI_EMBEDDED_RESPONSE" &&
    event.data.requestId === "auth-1"
  ) {
    const user = event.data.payload; // Same result as Web Component
  }
});
```

**Same functionality, different invocation pattern.**

## Response handling

Responses from the embedded app are sent via `postMessage` and can be identified by checking the message type:

```javascript theme={null}
window.addEventListener("message", (event) => {
  // Handle responses
  if (event.data?.type === "CORTI_EMBEDDED_RESPONSE") {
    const { requestId, success, payload, error } = event.data;
    // Handle response
  }

  // Handle events
  if (event.data?.type === "CORTI_EMBEDDED_EVENT") {
    const { event: eventType, payload } = event.data;
    // Handle event
  }
});
```

## Events

Corti Assistant dispatches events to notify your application of state changes and important updates. When using the PostMessage API, these events are wrapped in the `CORTI_EMBEDDED_EVENT` message type.

### Event format translation

Core events documented in the [Events Reference](/assistant/events) are wrapped for postMessage delivery:

**Core Event Structure:**

```json theme={null}
{
  "event": "recording.started",
  "confidential": false,
  "payload": {
    "mode": "dictation",
    "language": "en",
    "interactionId": "int_123"
  }
}
```

**PostMessage wrapper:**

```json theme={null}
{
  "type": "CORTI_EMBEDDED_EVENT",
  "event": "recording.started",
  "confidential": false,
  "payload": {
    "mode": "dictation",
    "language": "en",
    "interactionId": "int_123"
  }
}
```

### Listening for events

Set up a message listener to receive events from the embedded Assistant:

```javascript Listening for Events expandable theme={null}
const ALLOWED_ORIGINS = [
  "https://assistant.eu.corti.app",
  "https://assistantmd.eu.corti.app",
  "https://assistant.us.corti.app",
];

window.addEventListener("message", (event) => {
  // Validate origin for security
  if (!ALLOWED_ORIGINS.includes(event.origin)) {
    return;
  }

  // Check for Corti events
  if (event.data?.type === "CORTI_EMBEDDED_EVENT") {
    const { event: eventName, confidential, payload } = event.data;

    // Handle different event types
    switch (eventName) {
      case "recording.started":
        console.log("Recording started:", payload);
        break;
      case "recording.paused":
        console.log("Recording paused:", payload);
        break;
      case "document.generated":
        console.log("Document generated:", payload);
        handleDocumentGenerated(payload);
        break;
      case "error.triggered":
        console.error("Error occurred:", payload);
        break;
      default:
        console.log("Unknown event:", eventName, payload);
    }
  }
});

function handleDocumentGenerated(payload) {
  const { documentId, documentName, interactionId } = payload;
  // Update your UI, sync to backend, etc.
}
```

### Available events

For a complete list of events and their payload structures, see the [Events Overview](/assistant/events).

Common events include:

* `recording.started` - Recording has started
* `recording.paused` - Recording has paused
* `document.generated` - Document has been generated
* `document.updated` - Document has been edited
* `document.synced` - Document synced to external system
* `error.triggered` - An error occurred

### Legacy events

<Note>
  The embedded Assistant also dispatches [legacy events](/assistant/events/legacy-events) using camelCase names (e.g., `recordingStarted`, `documentGenerated`). These are deprecated and will be removed in a future version.
</Note>

## Error handling

Always handle errors when making requests:

```javascript Error Handling expandable theme={null}
try {
  const result = await sendMessage("auth", {
    accessToken: "your-access-token",
    refreshToken: "your-refresh-token",
    id_token: "your-id-token",
    token_type: "Bearer",
  });
  console.log("Authentication successful:", result);
} catch (error) {
  console.error("Authentication failed:", error.message);
  // Handle authentication failure
}
```

## Security considerations

When using `postMessage`, always:

1. **Validate message origin**: Check `event.origin` to ensure messages come from trusted sources
2. **Use specific target origins**: Replace `'*'` with the specific origin when possible
3. **Sanitize data**: Never trust data from postMessage without validation

```javascript Security Best Practices expandable theme={null}
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 '*'
```

## Next steps

* Review the [OAuth Authentication Guide](/assistant/authentication) to set up user authentication
* See the [API Reference](/assistant/api-reference) for all available methods and their parameters
* Learn about [events](/assistant/events) that the embedded app can send
* Check out the [Window API](/assistant/window-api) for same-origin integrations

<Note>
  Please [contact us](https://help.corti.app) for help or questions.
</Note>
