Files
helix-engage-server/docs/website-widget.md
saridsa2 9cb4d1c122 docs: website widget operations guide + archive widget source
- 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>
2026-04-21 06:39:23 +05:30

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

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 existsGET /api/config/widget should return {enabled: true, key: "..."}
  4. Generate key if neededPOST /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:

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.