- 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>
9.1 KiB
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>):
<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
# 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
allowedOriginswhitelist - Empty whitelist = test mode (any origin allowed)
For Production
Before going live on a real hospital website:
- Set
allowedOriginsto the hospital's domain(s):["https://ramaiahmedical.com", "https://www.ramaiahmedical.com"] - Ensure
WIDGET_SECRETenv 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
- Visitor opens widget → Chat tab shows name + phone form
- Visitor enters name + phone →
POST /api/widget/chat-start→ returnsleadId- Creates or finds existing lead by phone (deduplication)
- Visitor types a message →
POST /api/widget/chatwithleadId+ message history- AI streams a reply via Server-Sent Events
- AI has tools: branch selection, doctor search, slot lookup, booking suggestions
- After conversation ends, transcript is saved to lead activity timeline
- CC agent sees the WhatsApp/chat history when calling the patient back
How the Booking Flow Works
- Visitor opens widget → Book tab
- Selects department → fetches doctor list
- Selects doctor → fetches available dates/slots
- Enters patient name, phone, chief complaint
POST /api/widget/bookwith captcha token- Creates patient + lead + appointment in the platform
- Shows confirmation with reference number
How Lead Capture Works
- Visitor opens widget → Contact tab
- Enters name, phone, interest, optional message
POST /api/widget/leadwith captcha token- Creates lead with source "Website Widget"
- 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
cd packages/widget-src
npm install
npm run build
# Output goes to ../../public/widget.js (configured in vite.config.ts)
After rebuilding:
- Commit
public/widget.js - Build Docker image and deploy sidecar
- Widget auto-updates on next page load (1h cache)
Deployment Checklist
First-time setup on a new tenant:
- Sidecar serves widget.js — verify
https://{tenant}.engage.healix360.net/widget.jsreturns JS, not HTML - Caddy routing —
/widget.jsmust route to sidecar, not frontend. Add to the@api pathmatcher in Caddyfile:@api path /api/* /widget.js /graphql /auth/* ... - Widget config exists —
GET /api/config/widgetshould return{enabled: true, key: "..."} - Generate key if needed —
POST /api/widget/keys/generate - Set allowed origins (for production) —
PUT /api/config/widgetwithallowedOrigins: ["https://hospital.com"] - Test embed — paste the
<script>tag into a test HTML page and verify the widget appears - Verify AI — start a chat, confirm AI responds (requires
OPENAI_API_KEYorANTHROPIC_API_KEYin 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
- Check browser console for errors
- Verify
VITE_API_URLin the frontend build points to the sidecar URL (notlocalhost) - Verify
/api/config/widgetreturnsenabled: trueandembed.loginPage: true - Verify
/widget.jsreturns 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:
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.