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