mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-05-18 20:08:19 +00:00
- Comprehensive docs: embed snippet, key management, API endpoints, chat/booking/contact flows, lead dedup, reCAPTCHA, branding, deploy checklist, troubleshooting - Widget Preact source archived in packages/widget-src/ (was only on local machine, not tracked in any repo) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
240 lines
9.1 KiB
Markdown
240 lines
9.1 KiB
Markdown
# Website Chat Widget — Operations Guide
|
|
|
|
## Overview
|
|
|
|
A floating chat/booking/contact widget that hospitals embed on their website via a single `<script>` tag. Visitors can:
|
|
- **Chat** with an AI assistant (powered by OpenAI/Anthropic)
|
|
- **Book** appointments (department → doctor → date → slot)
|
|
- **Contact** the hospital (name, phone, interest — creates a lead in the CRM)
|
|
|
|
All interactions create or update leads in Helix Engage, so CC agents see the full visitor journey when they call back.
|
|
|
|
---
|
|
|
|
## Embed Snippet
|
|
|
|
Add this to any page on the hospital website (before `</body>`):
|
|
|
|
```html
|
|
<script src="https://ramaiah.engage.healix360.net/widget.js"
|
|
data-key="956018d178194fb9.313657fbc8a912b9cf8c93b9a51dfb209022fcd9910bd5abc7aa16dfaacf98a3">
|
|
</script>
|
|
```
|
|
|
|
| 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 `<script>` tag into a test HTML page and verify the widget appears
|
|
7. **Verify AI** — start a chat, confirm AI responds (requires `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` in sidecar env)
|
|
|
|
### Current deployment (Ramaiah):
|
|
|
|
| Item | Value |
|
|
|---|---|
|
|
| Widget URL | `https://ramaiah.engage.healix360.net/widget.js` |
|
|
| Site Key | `956018d178194fb9.313657fbc8a912b9cf8c93b9a51dfb209022fcd9910bd5abc7aa16dfaacf98a3` |
|
|
| Site ID | `956018d178194fb9` |
|
|
| Allowed Origins | Empty (test mode — any origin) |
|
|
| Login Page Widget | Enabled |
|
|
| reCAPTCHA | Configured (key returned by `/api/widget/init`) |
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Widget doesn't appear
|
|
1. Check browser console for errors
|
|
2. Verify `VITE_API_URL` in the frontend build points to the sidecar URL (not `localhost`)
|
|
3. Verify `/api/config/widget` returns `enabled: true` and `embed.loginPage: true`
|
|
4. Verify `/widget.js` returns actual JavaScript (not HTML from the frontend catch-all)
|
|
|
|
### "leadId required" error
|
|
The chat requires a `chat-start` call first (name + phone → leadId). If the widget skips this step, it's using an old `widget.js`. Clear browser cache or deploy the correct version from commit `aa41a2a`.
|
|
|
|
### Chat returns "AI not configured"
|
|
Missing `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` in sidecar environment. Check with:
|
|
```bash
|
|
docker exec sidecar-ramaiah env | grep -i 'OPENAI\|ANTHROPIC\|AI_PROVIDER'
|
|
```
|
|
|
|
### CORS errors
|
|
The widget key guard validates the request origin against `allowedOrigins`. If empty, any origin is allowed. If set, the host website's domain must be in the list.
|
|
|
|
### Widget shows on login page but not on hospital website
|
|
The login page injection code is in `helix-engage/src/pages/login.tsx`. For external hospital websites, the embed snippet must be manually added to their HTML. There's no automatic injection for third-party sites.
|