# @meetelise/hc-scheduler-widget

An embeddable healthcare scheduling widget powered by EliseAI. Adds a modal-based appointment scheduler to any website with a single script tag.

## Installation

Add the following script tag to your page:

```html
<script type="module">
  import HCScheduler from "https://cdn.eliseai.com/@meetelise/hc-scheduler-widget";

  HCScheduler.start({
    organizationId: "your-organization-id",
  });
</script>
```

This loads the widget, registers the `eliseAi.hc` global API, and makes the scheduling modal available on your page.

## Quick Start

```html
<button onclick="eliseAi.hc.openScheduling()">Schedule an Appointment</button>

<script type="module">
  import HCScheduler from "https://cdn.eliseai.com/@meetelise/hc-scheduler-widget";

  HCScheduler.start({
    organizationId: "your-organization-id",
  });
</script>
```

That's it — clicking the button opens the scheduling modal.

## Configuration

`HCScheduler.start()` accepts the following options:

| Option           | Type     | Required | Description                                               |
| ---------------- | -------- | -------- | --------------------------------------------------------- |
| `organizationId` | `string` | Yes      | Your EliseAI organization ID.                             |
| `utm_source`     | `string` | No       | Default UTM source applied to all scheduling sessions.    |
| `utm_medium`     | `string` | No       | Default UTM medium applied to all scheduling sessions.    |
| `utm_campaign`   | `string` | No       | Default UTM campaign applied to all scheduling sessions.  |
| `utm_term`       | `string` | No       | Default UTM term applied to all scheduling sessions.      |
| `utm_content`    | `string` | No       | Default UTM content applied to all scheduling sessions.   |

### Example with UTM defaults

```html
<script type="module">
  import HCScheduler from "https://cdn.eliseai.com/@meetelise/hc-scheduler-widget";

  HCScheduler.start({
    organizationId: "your-organization-id",
    utm_source: "website",
    utm_medium: "referral",
    utm_campaign: "spring-promo",
    utm_term: "obgyn",
    utm_content: "homepage-hero",
  });
</script>
```

## API

Once `HCScheduler.start()` has been called, the global `eliseAi.hc` object exposes:

### `eliseAi.hc.openScheduling(options?)`

Opens the scheduling modal. Can be called with no arguments or with an options object:

| Option           | Type     | Description                                                                               |
| ---------------- | -------- | ----------------------------------------------------------------------------------------- |
| `organizationId` | `string` | **Overrides** the widget-level `organizationId` for this session.                         |
| `providerId`     | `string` | Opens scheduling pre-filtered to a specific provider.                                     |
| `locationId`     | `string` | Opens scheduling pre-filtered to a specific location.                                     |
| `utm_source`     | `string` | UTM source for this session. **Overrides** the widget-level `utm_source` if provided.     |
| `utm_medium`     | `string` | UTM medium for this session. **Overrides** the widget-level `utm_medium` if provided.     |
| `utm_campaign`   | `string` | UTM campaign for this session. **Overrides** the widget-level `utm_campaign` if provided. |
| `utm_term`       | `string` | UTM term for this session. **Overrides** the widget-level `utm_term` if provided.         |
| `utm_content`    | `string` | UTM content for this session. **Overrides** the widget-level `utm_content` if provided.   |

### `eliseAi.hc.close()`

Closes the scheduling modal programmatically.

## Provider & Location Pages

You can create provider-specific or location-specific booking buttons by passing `providerId` or `locationId` to `openScheduling()`:

```html
<!-- Provider-specific booking -->
<button onclick="eliseAi.hc.openScheduling({ providerId: '1' })">
  Book with Dr. Stiles
</button>

<!-- Location-specific booking -->
<button onclick="eliseAi.hc.openScheduling({ locationId: '1' })">
  Book at Irving
</button>

<!-- Both provider and location -->
<button
  onclick="eliseAi.hc.openScheduling({ providerId: '22', locationId: '55' })"
>
  Book with Dr. Riley at Irving
</button>
```

## UTM Tracking

UTM parameters are passed through to the scheduling URL so you can track where appointments originate.

### How UTM values flow

1. **Widget-level defaults** — Set any combination of `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, and `utm_content` in `HCScheduler.start()`. These apply to every `openScheduling()` call.
2. **Per-call overrides** — Pass any of those same parameters to `openScheduling()`. These take priority over the widget-level defaults.

### Parsing UTM from the page URL

A common pattern is to read UTM parameters from the current page's URL and pass them into the widget:

```html
<script type="module">
  import HCScheduler from "https://cdn.eliseai.com/@meetelise/hc-scheduler-widget";

  const params = new URLSearchParams(window.location.search);

  HCScheduler.start({
    organizationId: "your-organization-id",
    utm_source: params.get("utm_source") || undefined,
    utm_medium: params.get("utm_medium") || undefined,
    utm_campaign: params.get("utm_campaign") || undefined,
    utm_term: params.get("utm_term") || undefined,
    utm_content: params.get("utm_content") || undefined,
  });
</script>
```

With this setup, visiting `https://yoursite.com?utm_source=google&utm_medium=cpc&utm_campaign=spring&utm_term=obgyn&utm_content=ad-123` will automatically pass those values through to the scheduler.

### Important: Cookie-stored UTM values

If your site stores UTM parameters in cookies (e.g. capturing them on first visit and persisting them across pages), **the widget will not read those cookies automatically**. You are responsible for reading the cookie values yourself and passing them to the widget explicitly:

```html
<script type="module">
  import HCScheduler from "https://cdn.eliseai.com/@meetelise/hc-scheduler-widget";

  // Example: read UTM values from your own cookies
  function getCookie(name) {
    const match = document.cookie.match(
      new RegExp("(^| )" + name + "=([^;]+)"),
    );
    return match ? decodeURIComponent(match[2]) : undefined;
  }

  const params = new URLSearchParams(window.location.search);

  HCScheduler.start({
    organizationId: "your-organization-id",
    utm_source: params.get("utm_source") || getCookie("utm_source"),
    utm_medium: params.get("utm_medium") || getCookie("utm_medium"),
    utm_campaign: params.get("utm_campaign") || getCookie("utm_campaign"),
    utm_term: params.get("utm_term") || getCookie("utm_term"),
    utm_content: params.get("utm_content") || getCookie("utm_content"),
  });
</script>
```

The widget operates inside an iframe on a different domain and **cannot access your site's cookies**. Always pass UTM values through the JavaScript API.

## Google Tag Manager (GTM) Events

The scheduling iframe emits `postMessage` events to the parent window that can be forwarded to GTM's `dataLayer`. Add the following script to your page to listen for these events and push them into the data layer:

```html
<script>
  const GTM_POST_MESSAGE_TYPE = "elise-gtm-event";

  function isGtmPostMessage(message) {
    return (
      message !== null &&
      typeof message === "object" &&
      message.type === GTM_POST_MESSAGE_TYPE &&
      typeof message.event === "string" &&
      message.properties !== null &&
      typeof message.properties === "object"
    );
  }

  function handleGtmMessage(e) {
    if (!isGtmPostMessage(e.data)) return;

    const { event, properties } = e.data;

    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({ event, ...properties });
  }

  window.addEventListener("message", handleGtmMessage);
</script>
```

### GTM Events

| Event                        | Payload                              | Description                                                                                                                                    |
| ---------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `patientTypeSelected`        | `{ patientType: "newPatient" \| "existingPatient" }` | User selects new or existing patient on the welcome page                                                                         |
| `phoneNumberSubmitted`       | —                                    | New patient submits their phone number for verification                                                                                        |
| `verificationCodeResent`     | —                                    | User requests a new verification code to be sent                                                                                               |
| `verificationCodeSubmitted`  | —                                    | User submits their phone verification code                                                                                                     |
| `chatMessageSent`            | —                                    | User sends a message in the AI triage chat                                                                                                     |
| `appointmentTypeSelected`    | —                                    | Chat AI determines an appointment type and user clicks the schedule button                                                                     |
| `personalDetailsUpdated`     | —                                    | New patient submits their personal details (name, DOB, address, etc.)                                                                          |
| `insuranceUpdated`           | —                                    | Existing patient updates their insurance during the confirm details step                                                                       |
| `continuedWithSelfPay`       | —                                    | Patient elects to continue with self-pay after insurance ineligibility                                                                         |
| `patientTransferred`         | `{ reason: string }`                 | Patient is transferred out of the scheduling flow. Reasons include: `"noAvailability"`, `"workflow"`, or an EligibilityCheckReason value (e.g., `"NOT_ACCEPTED"`, `"UNKNOWN"`) |
| `bookingSucceeded`           | —                                    | Appointment(s) successfully booked                                                                                                             |
| `upcomingRescheduleInitiated`| `{ appointmentIds: string[] }`       | Existing patient initiates rescheduling from the snapshot page                                                                                 |
| `upcomingRescheduleSucceeded`| —                                    | Rescheduled appointment(s) successfully confirmed                                                                                              |
| `upcomingAppointmentCanceled`| `{ appointmentIds: string[] }`       | Existing patient cancels upcoming appointment(s)                                                                                               |
| `upcomingAppointmentConfirmed`| `{ appointmentIds: string[] }`      | Existing patient confirms upcoming appointment(s)                                                                                              |

## Webhooks

When a scheduling session expires, EliseAI sends an HTTP `POST` request to a webhook URL you provide. The request body contains a summary of that session — patient information, UTM attribution, and the chronological sequence of events the patient went through.

### Example payload

```json
{
  "session_id": "a1b2c3d4-1111-2222-3333-444455556666",
  "sent_at": "2026-06-05T16:30:00Z",
  "properties": {
    "session_id": "a1b2c3d4-1111-2222-3333-444455556666",
    "patient_id": "987654",
    "patient_type": "new_profile_created",
    "device_type": "mobile",
    "session_started_at": "2026-06-05T16:00:00Z",
    "timezone": "America/Chicago",
    "utm": { "utm_source": "curve", "utm_content": "cv_abc123", "utm_medium": "referral" },
    "session_events": [
      [
        { "type": "auth_requested",            "occurred_at": "2026-06-05T16:00:05Z" },
        { "type": "auth_2fa_verified",         "occurred_at": "2026-06-05T16:00:40Z" },
        { "type": "appointment_type_selected", "occurred_at": "2026-06-05T16:01:10Z" },
        { "type": "booking_confirmed",         "occurred_at": "2026-06-05T16:02:30Z" }
      ],
      [
        { "type": "reschedule_initiated", "occurred_at": "2026-06-05T16:05:00Z" },
        { "type": "availability_shown",   "occurred_at": "2026-06-05T16:05:20Z" },
        { "type": "dropoff_during_viewing_availability_reschedule", "occurred_at": "2026-06-05T16:05:20Z" }
      ]
    ]
  }
}
```

### Payload reference

| Field                          | Type                              | Description                                                                                                                                |
| ------------------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `session_id`                   | `string`                          | UUID identifying the session                                                                                                               |
| `sent_at`                      | `string`                          | ISO 8601 timestamp at which the webhook was sent                                                                                           |
| `properties.session_id`        | `string`                          | UUID identifying the session (mirrors the top-level `session_id`)                                                                          |
| `properties.patient_id`        | `string`                          | Patient identifier                                                                                                                         |
| `properties.patient_type`      | `string`                          | Patient classification for the session, e.g. `"new_profile_created"`                                                                       |
| `properties.device_type`       | `string`                          | Device used for the session, e.g. `"mobile"` or `"desktop"`                                                                                |
| `properties.session_started_at`| `string`                          | ISO 8601 timestamp at which the session began                                                                                             |
| `properties.timezone`          | `string`                          | IANA timezone of the session, e.g. `"America/Chicago"`                                                                                     |
| `properties.utm`               | `object`                          | UTM attribution: `utm_source`, `utm_content`, `utm_medium`                                                                                 |
| `properties.session_events`    | `Array<Array<{ type, occurred_at }>>` | Groups of ordered events capturing the chronological steps the patient took (e.g. `auth_requested`, `booking_confirmed`, `dropoff_*`). Each event has a `type` and an ISO 8601 `occurred_at`. |

### What we need from you

To enable webhooks, please provide:

1. **Webhook URL** — the endpoint we should `POST` each payload to.
2. **Credentials** — how we should authenticate the request (e.g. a bearer token, an API key header, or basic auth).
3. **Custom properties** — any additional properties you want included in the `properties` object of each payload (for example, a `bridge_token` to correlate the session with a referral source).

## Behavior

- **Modal sizing** — On screens wider than 600px, the modal appears centered with a max size of 900×900px. On smaller screens, it expands to fullscreen.
- **Closing** — Users can close the modal by pressing **Escape** or clicking the **×** button.
- **Font** — The widget automatically loads the [Inter](https://rsms.me/inter/) font if it isn't already present on the page.
