# Set up the SDK

Follow these steps once per environment. Each step says who does it.

## Enable the SDK[​](#enable-the-sdk "Direct link to Enable the SDK")

Nabla

Contact <connect@nabla.com> to enable the Nabla Connect SDK for your organization.

## Allowlist your origins[​](#allowlist-your-origins "Direct link to Allowlist your origins")

Nabla Admin

Nabla only exchanges messages with pages whose origin you have allowlisted.

1. Open [Nabla Connect Admin](https://app.nabla.com/admin/nabla-connect).
2. Under **Target origin allowlist (front integration)**, click **Add target origin**.
3. Enter one or more origins, comma-separated, for example `https://ehr.example.com,https://staging.ehr.example.com`.
4. Save.

An origin is `scheme://host:port`, with no path and no trailing slash. It must match the page that embeds the iframe exactly, including scheme, hostname, and port. Use HTTPS origins.

danger

**Removing an origin breaks live launches.** `POST /encounters` rejects the removed origin immediately, so an integration that still uses it stops creating encounters. Encounters already launched keep the origin they were launched with.

## Pass `target_origin`[​](#pass-target_origin "Direct link to pass-target_origin")

Backend

When your backend calls [`POST /encounters`](/connect/reference/create-or-update-encounters.md), set `target_origin` to the origin of the page that will embed the iframe. It must be in the allowlist, and a well-formed origin. Otherwise the call returns `400` with `target_origin <origin> is not in the allowlist` or `target_origin must be a well-formed URL origin`.

```
{

  "external_encounter_id": "enc-42",

  "external_provider_id": "prov-7",

  "provider_email": "dr.smith@example.com",

  "target_origin": "https://ehr.example.com"

}
```

Without `target_origin`, the iframe loads normally but sends nothing to the parent page, and `ready` rejects with [`TIMEOUT`](/connect/sdk/guides/errors.md#timeout).

## Embed the iframe[​](#embed-the-iframe "Direct link to Embed the iframe")

Browser

Same requirements as any Connect embed, with one difference: render the iframe without a `src`. The SDK loads the encounter URL for you. See [Launch encounters](/connect/guides/launch-encounters.md#embed-nabla-in-an-iframe) for the `allow` list.

```
<iframe id="nabla" allow="microphone; clipboard-write"></iframe>
```

A client is bound to its iframe. If another client is created on the same iframe, or another document or origin loads in it, the client emits [`CONTEXT_CHANGED`](/connect/sdk/guides/errors.md#context-changed). Call `destroy()` before removing or replacing the iframe. If the iframe reloads, the client reconnects on its own.

## Install[​](#install "Direct link to Install")

Browser

```
npm install @nabla/connect-sdk
```

## Create the client[​](#create-the-client "Direct link to Create the client")

Browser

Pass the iframe and the encounter URL from your backend to [`NablaConnect.create`](/connect/sdk/reference/variables/NablaConnect.md). The SDK takes the Connect origin from the URL. It must be HTTPS, except on `localhost`, `127.0.0.1` and `[::1]`.

Encounter URLs only, from [`POST /encounters`](/connect/reference/create-or-update-encounters.md) or [`POST /encounters/url`](/connect/reference/generate-encounters-url.md). A settings URL ([`POST /settings/url`](/connect/reference/generate-settings-url.md)) is not an encounter: load it in a plain iframe without the SDK. A client created on it never gets a first state.

```
import { NablaConnect } from "@nabla/connect-sdk";



const iframe = document.getElementById("nabla") as HTMLIFrameElement;

const client = NablaConnect.create({ iframe, url: encounterUrl }); // encounterUrl comes from your backend



client.addEventListener("stateChanged", ({ detail: state }) => {

  recordButton.disabled = !state.availableActions.includes("START_RECORDING");

  closeButton.disabled = !state.canBeClosed;

});



client.addEventListener("noteExported", ({ detail }) => {

  saveToChart(detail.note, detail.transcript);

});



client.addEventListener("error", ({ detail: error }) => {

  console.warn("Nabla Connect", error.code, error.reason);

});



try {

  const state = await client.ready; // the stateChanged listener above has already received the first state

  if (state.availableActions.includes("START_RECORDING")) {

    await client.startRecording();

  }

} catch (error) {

  // A NablaConnectError. TIMEOUT keeps the client listening: the first state still arrives on stateChanged.

  // Any other code ends the client: create a new one to retry.

}
```

`create` is synchronous. `ready` resolves with the first state once the iframe has loaded and is connected, or rejects 30 seconds after `create`.

`create` throws a `TypeError` when the iframe does not belong to a window. Nothing else throws: every failure is a [`NablaConnectError`](/connect/sdk/reference/classes/NablaConnectError.md). `ready` rejects with `UNSUPPORTED_VERSION`, `INVALID_ORIGIN`, `TIMEOUT`, `CONTEXT_CHANGED`, `UNKNOWN`, or `DESTROYED`. A rejected `ready` ends the client, so create another one to retry. `TIMEOUT` is the exception: the iframe may still be waiting on the provider (sign-in, onboarding), so the client keeps listening and `stateChanged` delivers the first state when it comes. Commands called before that state reject with `TIMEOUT`. A failure before `ready` rejects `ready` only: it does not reach the `error` event, except a failure that ends the client after a `TIMEOUT`, which is reported on `error` since `ready` already settled.

If `ready` rejects with `TIMEOUT`, see [Troubleshooting](/connect/sdk/guides/errors.md#troubleshooting).
