# Website Chat Widget — Operations Guide
## Overview
A floating chat/booking/contact widget that hospitals embed on their website via a single `
```
| Parameter | Description |
|---|---|
| `src` | The sidecar URL + `/widget.js` |
| `data-key` | Site key — generated and rotatable from the admin portal |
The widget renders in a **shadow DOM** — its styles don't leak into or get affected by the host website's CSS.
---
## Admin Configuration
### Settings Page
URL: `https://ramaiah.engage.healix360.net/settings/widget` (login as admin/supervisor)
| Setting | Description |
|---|---|
| **Enabled** | Master kill switch — when off, widget.js no-ops |
| **Site Key** | Read-only HMAC-signed key. Copy-to-clipboard for the embed snippet |
| **Site ID** | Read-only identifier |
| **Rotate Key** | Generates a new key, invalidates the old embed snippet |
| **Hosting URL** | Public base URL for widget.js. Leave blank to use same origin as sidecar |
| **Allowed Origins** | Whitelist of domains allowed to embed. Empty = any origin (test mode) |
| **Show on Login Page** | Toggle to display widget on the Helix Engage login page |
### Configuration API
```bash
# Read config (public — no auth)
curl https://ramaiah.engage.healix360.net/api/config/widget
# Read full config (admin)
curl https://ramaiah.engage.healix360.net/api/config/widget/admin
# Update config
curl -X PUT https://ramaiah.engage.healix360.net/api/config/widget \
-H "Content-Type: application/json" \
-d '{"enabled": true, "allowedOrigins": ["https://ramaiahmedical.com"]}'
# Rotate site key (invalidates old embeds)
curl -X POST https://ramaiah.engage.healix360.net/api/config/widget/rotate-key
```
---
## Widget Key Security
- Key format: `{siteId}.{hmacSignature}`
- HMAC computed as: `sha256(siteId, secret=WIDGET_SECRET env var)`
- Validated with `timingSafeEqual()` to prevent timing attacks
- Origin checked against `allowedOrigins` whitelist
- Empty whitelist = test mode (any origin allowed)
### For Production
Before going live on a real hospital website:
1. Set `allowedOrigins` to the hospital's domain(s): `["https://ramaiahmedical.com", "https://www.ramaiahmedical.com"]`
2. Ensure `WIDGET_SECRET` env var is set in the sidecar (auto-generated on first run if missing)
---
## Widget API Endpoints
All endpoints require `WidgetKeyGuard` (key as query param `?key=...` or header `X-Widget-Key`).
| Endpoint | Method | Purpose |
|---|---|---|
| `/api/widget/init` | GET | Returns brand name, logo, colors, reCAPTCHA key |
| `/api/widget/doctors` | GET | All doctors with departments, specialties, fees, visit slots |
| `/api/widget/slots?doctorId=X&date=YYYY-MM-DD` | GET | Available time slots for a doctor on a date |
| `/api/widget/book` | POST | Book appointment (requires captcha token) |
| `/api/widget/lead` | POST | Create lead from contact form (requires captcha token) |
| `/api/widget/chat-start` | POST | Start chat session — body: `{name, phone}`, returns `{leadId}` |
| `/api/widget/chat` | POST | Stream AI reply — body: `{leadId, messages[], branch?}`, returns SSE stream |
---
## How the Chat Flow Works
1. Visitor opens widget → Chat tab shows name + phone form
2. Visitor enters name + phone → `POST /api/widget/chat-start` → returns `leadId`
- Creates or finds existing lead by phone (deduplication)
3. Visitor types a message → `POST /api/widget/chat` with `leadId` + message history
- AI streams a reply via Server-Sent Events
- AI has tools: branch selection, doctor search, slot lookup, booking suggestions
4. After conversation ends, transcript is saved to lead activity timeline
5. CC agent sees the WhatsApp/chat history when calling the patient back
---
## How the Booking Flow Works
1. Visitor opens widget → Book tab
2. Selects department → fetches doctor list
3. Selects doctor → fetches available dates/slots
4. Enters patient name, phone, chief complaint
5. `POST /api/widget/book` with captcha token
6. Creates patient + lead + appointment in the platform
7. Shows confirmation with reference number
---
## How Lead Capture Works
1. Visitor opens widget → Contact tab
2. Enters name, phone, interest, optional message
3. `POST /api/widget/lead` with captcha token
4. Creates lead with source "Website Widget"
5. Shows success confirmation
---
## Lead Deduplication
All three flows (chat, book, contact) use `CallerResolutionService` to find existing leads by phone number. If a visitor chats, then books, then contacts — all within 24 hours — they create ONE lead, not three. Activities are appended to the same lead.
---
## reCAPTCHA Protection
- Booking and lead endpoints use reCAPTCHA v3 (invisible — no user friction)
- Chat endpoints do NOT use reCAPTCHA (session already verified by name+phone gate)
- reCAPTCHA site key returned by `/api/widget/init`
- Server validates tokens via Google reCAPTCHA API
- Captcha can be bypassed for webhooks using `captchaToken: "webhook-bypass"`
---
## Branding & Theming
The widget pulls branding from the sidecar theme config:
- **Hospital name** — displayed in the widget header
- **Logo** — shown in the header
- **Brand color** — applied to buttons, links, active states
- **Location** — shown under the hospital name
Configure via: `https://ramaiah.engage.healix360.net/settings` → Theme section
---
## Widget Source Code
| Location | Description |
|---|---|
| `packages/widget-src/` | Preact source (chat.tsx, booking.tsx, contact.tsx, api.ts, etc.) |
| `public/widget.js` | Compiled IIFE bundle (served by NestJS static assets) |
| `src/widget/` | Backend API (controller, service, chat service, key guard) |
| `src/config/widget-keys.service.ts` | Key generation + HMAC validation |
| `src/config/widget-config.service.ts` | Config file management (data/widget.json) |
### Rebuilding widget.js
```bash
cd packages/widget-src
npm install
npm run build
# Output goes to ../../public/widget.js (configured in vite.config.ts)
```
After rebuilding:
1. Commit `public/widget.js`
2. Build Docker image and deploy sidecar
3. Widget auto-updates on next page load (1h cache)
---
## Deployment Checklist
### First-time setup on a new tenant:
1. **Sidecar serves widget.js** — verify `https://{tenant}.engage.healix360.net/widget.js` returns JS, not HTML
2. **Caddy routing** — `/widget.js` must route to sidecar, not frontend. Add to the `@api path` matcher in Caddyfile:
```
@api path /api/* /widget.js /graphql /auth/* ...
```
3. **Widget config exists** — `GET /api/config/widget` should return `{enabled: true, key: "..."}`
4. **Generate key if needed** — `POST /api/widget/keys/generate`
5. **Set allowed origins** (for production) — `PUT /api/config/widget` with `allowedOrigins: ["https://hospital.com"]`
6. **Test embed** — paste the `