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

# Embedding Assistant in React Web App

> Complete guide for integrating Corti Assistant into a React web application

This guide walks you through integrating Corti Assistant into a React web application. You'll learn the key concepts, architecture patterns, and implementation approach using the Web Component API.

<Check>
  **Complete working example available**: This guide references the [basic React example](https://github.com/corticph/corti-examples/tree/main/embedded-assistant/react/basic-example) in our examples repository. Clone it to see a fully functional implementation, then follow this guide to understand how it works and adapt it to your needs.
</Check>

<Info>
  This guide uses ROPC (Resource Owner Password Credentials) authentication for simplicity and practicality in embedded scenarios where users are already authenticated in the host application. For production deployments, consider your organization's authentication requirements.
</Info>

***

## What you'll learn

By following this guide, you'll understand:

* How to architect a secure client-server integration
* Why ROPC authentication works well for embedded EHR scenarios
* How to use the Web Component API with React
* How to manage the interaction lifecycle programmatically
* Key customization points and production considerations

## Prerequisites

Before starting, ensure you have:

* **Node.js 18+** installed
* **Corti API access** with valid credentials
* **OAuth client** configured for ROPC (Resource Owner Password Credentials) flow
* **User account** created in Corti Console for authentication
* Basic familiarity with React and Express

<Tip>
  Don't have credentials yet? Visit [Corti Console](https://console.corti.app) to create a user and OAuth client. Ensure your OAuth client has "Direct Access Grants" enabled for ROPC flow.
</Tip>

***

## Architecture overview

The integration follows a client-server architecture:

**Backend (Express Server)**:

* Handles OAuth authentication using ROPC flow via `@corti/sdk`
* Provides configuration endpoint for environment-specific settings
* Keeps sensitive credentials server-side
* Runs on port 3000

**Frontend (React + Vite)**:

* Fetches auth tokens and configuration from backend
* Embeds Corti Assistant using `@corti/embedded-web` Web Component
* Manages interaction lifecycle (create, navigate, handle events)
* Uses React 19's Suspense for async data loading
* Runs on port 5173 (Vite default)

**Why this architecture?**

* **Security**: Credentials stay on the server, never exposed to the browser
* **Separation of concerns**: Auth logic separate from UI logic
* **Flexibility**: Easy to swap ROPC for other auth methods later
* **Development speed**: Simple to run and test locally

***

## Key concepts

### Authentication flow

1. **Backend authenticates** with Corti using stored credentials (ROPC)
2. **Frontend requests** tokens from backend `/api/auth` endpoint
3. **Web Component** receives tokens and authenticates the embedded session
4. **No user interaction** required - seamless experience

**Why ROPC?** Users are already logged into your application. Forcing them to re-authenticate with Corti via browser popup would be confusing and disruptive. ROPC allows seamless embedded authentication.

### Web Component integration

The integration uses `<CortiEmbeddedReact>`, a React wrapper around the standard Web Component:

```tsx theme={null}
import {
  CortiEmbeddedReact,
  useCortiEmbeddedApi,
} from "@corti/embedded-web/react";

// In your component:
<CortiEmbeddedReact
  ref={cortiRef}
  baseURL={baseUrl}
  visibility="visible"
  onReady={handleReady}
  onEvent={handleEvent}
  onError={handleError}
/>;
```

**Key benefits**:

* **Type-safe**: Full TypeScript support with IDE autocomplete
* **React-friendly**: Hooks-based API (`useCortiEmbeddedApi`)
* **Event handling**: Native React event props instead of addEventListener
* **Lifecycle management**: Automatic cleanup and ref management

### Interaction lifecycle

Every clinical encounter in Corti Assistant is called an "interaction":

1. **Create interaction** - Define encounter metadata (type, identifier, timestamps)
2. **Navigate to session** - Load the session view with recording interface
3. **User records** - Clinician documents the encounter
4. **Handle events** - React to document generation, errors, state changes

**Why create interactions programmatically?** This allows you to:

* Link Corti sessions to your EHR's encounter IDs
* Pre-fill encounter metadata from your system
* Control when and how sessions start
* Track interactions in your database

***

## Implementation guide

### Project structure

```text theme={null}
your-react-app/
├── server.ts              # Backend: Auth + config endpoints
├── src/
│   ├── App.tsx            # Root: Suspense boundary + data fetching
│   ├── components/
│   │   └── EmbeddedAssistant.tsx  # Main integration component
│   ├── lib/
│   │   └── auth.ts        # API calls to backend
│   └── types.ts           # TypeScript definitions
├── .env                   # Credentials (never commit!)
└── package.json
```

### Step 1: Backend setup

**Purpose**: Securely authenticate with Corti and provide tokens to the frontend.

**Key file**: `server.ts`

**What it does**:

* `/api/auth` - Uses `@corti/sdk` to perform ROPC authentication
* `/api/config` - Returns base URL based on environment (EU/US)
* CORS configured for local Vite dev server

**Implementation approach**:

```typescript theme={null}
// Uses Corti SDK for authentication
const auth = new CortiAuth({
  environment: process.env.CORTI_ENVIRONMENT,
  tenantName: process.env.CORTI_TENANT_NAME,
});

// ROPC flow - exchanges username/password for tokens
const tokenResponse = await auth.getRopcFlowToken({
  clientId: process.env.CORTI_CLIENT_ID,
  username: process.env.CORTI_USER_EMAIL,
  password: process.env.CORTI_USER_PASSWORD,
});
```

**See the full implementation**: [server.ts in example repo](https://github.com/corticph/corti-examples/blob/main/embedded-assistant/react/basic-example/server.ts)

### Step 2: Frontend data fetching

**Purpose**: Fetch auth tokens and config before rendering the Assistant.

**Key file**: `src/lib/auth.ts`

**What it does**:

* Promise-based API calls to backend
* Simple in-memory caching to prevent duplicate requests
* Separates data fetching logic from UI components

**Why separate?** Keeps components focused on rendering, makes it easy to add caching or error retry logic later.

**See the implementation**: [auth.ts in example repo](https://github.com/corticph/corti-examples/blob/main/embedded-assistant/react/basic-example/src/lib/auth.ts)

### Step 3: App root with suspense

**Purpose**: Coordinate parallel data fetching and handle loading states.

**Key file**: `src/App.tsx`

**Key pattern**: React 19's `use()` hook with Suspense for async data

```tsx theme={null}
function AppWithData({ configPromise, authPromise }) {
  const config = use(configPromise); // Suspends until resolved
  const authData = use(authPromise); // Fetched in parallel

  if (authData.error) {
    return <ErrorDisplay />;
  }

  return <EmbeddedAssistant baseURL={config.baseUrl} authData={authData} />;
}
```

**Why this pattern?**

* **Parallel fetching**: Config and auth load simultaneously
* **Declarative loading**: Suspense fallback handles loading UI automatically
* **Error boundaries**: Easy to add error handling at the boundary level

**Adapting for React 18?** Use `useEffect` + `useState` for data fetching instead. The rest of the pattern stays the same.

**See the implementation**: [App.tsx in example repo](https://github.com/corticph/corti-examples/blob/main/embedded-assistant/react/basic-example/src/App.tsx)

### Step 4: Embedded Assistant component

**Purpose**: Render the Web Component and manage the interaction lifecycle.

**Key file**: `src/components/EmbeddedAssistant.tsx`

**Core responsibilities**:

1. Render `<CortiEmbeddedReact>` with configuration
2. Use `useCortiEmbeddedApi` hook to access API methods
3. Handle `onReady` event to authenticate and create interaction
4. Manage status display and error states

**Critical pattern - preventing double initialization**:

```tsx theme={null}
const hasInitialized = useRef(false);

const handleReady = async () => {
  if (hasInitialized.current) return; // Guard against React StrictMode
  hasInitialized.current = true;

  // Now safe to authenticate and create interaction
};
```

**Why needed?** React 19's StrictMode intentionally double-invokes effects in development. Without this guard, you'd create two interactions.

**Event handling strategy**:

* `onReady` - Authenticate, create interaction, navigate to session
* `onEvent` - Log events (or handle specific ones like document.generated)
* `onError` - Display errors to user

**See the implementation**: [EmbeddedAssistant.tsx in example repo](https://github.com/corticph/corti-examples/blob/main/embedded-assistant/react/basic-example/src/components/EmbeddedAssistant.tsx)

***

## Running the example

Clone and run the complete example:

```bash theme={null}
git clone https://github.com/corticph/corti-examples.git
cd corti-examples/embedded-assistant/react/basic-example
npm install
cp .env.example .env
# Edit .env with your credentials
npm run dev
```

Open [http://localhost:5173](http://localhost:5173) to see the integration in action.

***

## Customization points

### Change authentication method

To use PKCE instead of ROPC, modify `server.ts` to use:

```typescript theme={null}
const tokenResponse = await auth.getPkceFlowToken({
  clientId,
  redirectUri,
  code: authorizationCode,
});
```

See [Authentication Guide](/assistant/authentication) for PKCE implementation details.

### Customize interaction creation

Modify the `createInteraction` call in `EmbeddedAssistant.tsx`:

```tsx theme={null}
const interaction = await api.createInteraction({
  assignedUserId: currentUserId, // Assign to specific clinician
  encounter: {
    identifier: ehrEncounterId, // Your system's encounter ID
    status: "in-progress", // Match your encounter status
    type: "first_consultation", // Match your encounter type
    period: {
      startedAt: encounterStartTime,
      endedAt: encounterEndTime, // For completed encounters
    },
  },
});
```

### Configure Assistant appearance

Call `api.configure()` after authentication to customize:

```tsx theme={null}
await api.configure({
  features: {
    aiChat: false, // Hide AI chat feature
    navigation: true, // Show navigation sidebar
  },
  appearance: {
    primaryColor: "#00539F", // Your brand color
  },
  locale: {
    interfaceLanguage: "en",
    dictationLanguage: "en-US",
  },
});
```

See [Configuration Reference](/assistant/configuration) for all options.

### Handle specific events

Listen for specific Assistant events:

```tsx theme={null}
const handleEvent = (event: CustomEvent) => {
  const { name: eventType, payload } = event.detail;

  switch (eventType) {
    case "document.generated":
      // Save document to your database
      saveDocumentToEhr(payload);
      break;

    case "interaction.created":
      // Track interaction in analytics
      analytics.track("corti_interaction_started", payload);
      break;

    case "recording.stopped":
      // Update UI state
      setRecordingStatus("stopped");
      break;
  }
};
```

See [Events Reference](/assistant/events/index) for all available events.

***

## Production considerations

### Environment variables

Never commit credentials to version control:

```bash theme={null}
# Use environment-specific .env files
.env.development  # Local dev credentials
.env.production   # Production credentials (injected via CI/CD)

# Add to .gitignore
echo ".env*" >> .gitignore
echo "!.env.example" >> .gitignore
```

### Error Handling

Add proper error boundaries and retry logic:

```tsx theme={null}
<ErrorBoundary fallback={<ErrorFallback />}>
  <Suspense fallback={<Loading />}>
    <EmbeddedAssistant />
  </Suspense>
</ErrorBoundary>
```

### Token refresh

Implement token refresh logic in your backend:

```typescript theme={null}
// Check token expiration
if (isTokenExpired(accessToken)) {
  // Refresh using refresh token
  const newTokens = await auth.refreshToken(refreshToken);
  // Update tokens in session
}
```

### CORS configuration

Update CORS for production domains:

```typescript theme={null}
app.use(
  cors({
    origin: [
      "https://your-production-domain.com",
      process.env.NODE_ENV === "development" ? "http://localhost:5173" : null,
    ].filter(Boolean),
    credentials: true,
  }),
);
```

***

## Next steps

This is a foundational integration. Extend it with:

* **Document export** - Capture `document.generated` events and save to your EHR
* **Session management** - Load existing interactions using `api.navigate("/session/<id>")`
* **Advanced configuration** - Customize features, appearance, and locale
* **Monitoring** - Add logging and analytics around Assistant usage
* **Multi-user support** - Handle authentication for multiple clinicians

<Info>
  **Advanced integration patterns** including document synchronization, session persistence, and multi-tenant setups will be available in Q2 2026. Check the [examples repository](https://github.com/corticph/corti-examples) for updates.
</Info>

***

## Troubleshooting

### CORS errors

If you see CORS errors, verify Vite's dev server port matches the `cors.origin` in `server.ts`. Vite defaults to 5173 but may use 5174+ if that port is taken.

**Fix**: Update the origin array in `server.ts` to match your Vite output:

```typescript theme={null}
origin: ["http://localhost:5174"]; // Match actual Vite port
```

### Authentication fails

* Verify credentials in `.env` match your Corti Console setup
* Ensure OAuth client has "Direct Access Grants" enabled for ROPC
* Check user account exists and is active in your Corti tenant
* Verify `CORTI_ENVIRONMENT` matches your tenant region (eu/us)

### Web Component not loading

* Check browser console for import errors
* Verify `@corti/embedded-web` package is installed correctly
* Ensure `baseURL` matches your region (`https://assistant.eu.corti.app`)
* Confirm microphone permissions are granted (required for recording)

### Double initialization in Dev Mode

This is expected with React 19 StrictMode. The `hasInitialized` ref guard prevents issues. In production builds, StrictMode is disabled and this won't occur.

***

## Related documentation

* [Web Component API Reference](/assistant/web-component-api)
* [Authentication Guide](/assistant/authentication)
* [Configuration Reference](/assistant/configuration)
* [API Reference](/assistant/api-reference)
* [Events Reference](/assistant/events/index)
* [Example Repository](https://github.com/corticph/corti-examples/tree/main/embedded-assistant)
