Skip to content
SKSuraj Kumar

Customer enquiries

Embedded Support Chatbot

Website chatbot integration

Embedded Support Chatbot is a working name used for this write-up. The client isn't identified, so their actual product name isn't either.

A customer-facing chat widget dropped into a site someone else built — answering from that site’s own pages, and handing over to a human when it should.

  • Automation
  • Integration
  • Chat widget
  • Retrieval
  • Embed
Role
Sole engineer. Widget build, embed and isolation strategy, retrieval over the client’s own content, escalation handoff, and the fallback path for when the model service is unavailable.
Work type
Integration build
Timeline
4 weeks build effort · 2025
Status
Case study published
Industry
Service businesses
Client
Private client work

Build effort, not elapsed calendar time. Engagements ran alongside study and employment, so this figure is the work itself rather than the span it sat in.

Interface built in code for this case study — not a client screen capture.

01Business problem

What was actually going wrong

A business wants a chatbot on the site it already has. Nobody wants to rebuild the site to get one, and nobody wants a bot that confidently answers questions about their pricing incorrectly.

Who feels it

  • Owners answering the same questions by phone all day
  • Visitors who will not fill in a form to ask one question
  • Whoever inherits the complaint when a bot promises something the business does not offer
  • The existing site, which nobody wants touched

The request arrives in the same shape every time: there is an existing site, built by someone else, maybe on a platform nobody has the credentials for any more, and the owner wants visitors to be able to ask questions instead of hunting through pages. Enquiries currently arrive by phone during opening hours and by contact form the rest of the time, and the same six questions account for most of them.

The constraint that makes this an integration problem rather than an AI problem is that I do not control the host page. I get one script tag. Whatever I ship has to survive the site’s existing CSS, not fight its z-index stack, not shift its layout, not slow its Lighthouse score enough for anyone to notice, and not break if the site is later edited by someone who has never heard of me.

The second constraint is that a wrong answer is worse than no answer. If the widget invents a price, a delivery time or a warranty term, the business inherits that claim in front of a customer. That risk is the reason most of the engineering here is about refusing to answer, not about answering.

02Product overview

What got built

One script tag, a widget isolated from the host page in a shadow root, answers grounded in the client’s own pages with a citation, and a handover to a real channel the moment the question stops being routine.

Engineering notes

  • Loader under three kilobytes that renders nothing and defers the widget bundle until idle or intent.
  • Shadow root with an adopted stylesheet, plus an explicit reset for the properties that still inherit across the boundary.
  • Pre-hydration click capture and replay, so the first interaction is never lost.
  • Similarity threshold enforced in code before the model is called, making refusal deterministic and not a matter of prompt compliance.

The embed is a single deferred script under three kilobytes. It renders nothing on load — it registers an idle callback and a click target, and the widget itself is only fetched when a visitor actually opens it or after the page has been quiet for a few seconds, whichever comes first. Until then the host page pays almost nothing.

Everything the widget renders lives inside a shadow root with its own styles, so the host site’s stylesheet cannot leak in and my styles cannot leak out. This is the part that decides whether an embed is a support burden or not: without isolation, every future edit to the host site is a potential visual bug in my widget, and I would be debugging someone else’s CSS for free indefinitely.

Answers come from the client’s own content, chunked from their pages and indexed at build time, never crawled live. Each answer carries the page it came from as a visible link. When retrieval returns nothing relevant enough, the widget says it does not know and offers the handover instead — that path is not a failure state, it is the designed behaviour for roughly a third of real conversations.

Handover means the conversation so far is attached to a WhatsApp deep link or an email draft, prefilled, so the visitor does not retype their question and the business does not start from nothing. If the model service is unreachable, the widget degrades to that same handover form and shows no error, so a bad day upstream looks like a contact form and not a broken site.

03Key features

What the software does, feature by feature

8 capabilities, of which 2 are model-backed. The rest are ordinary deterministic software, and the distinction is marked so the AI claim stays honest.

  • 01

    One script tag, no build step on the host

    A single deferred script with data attributes for the account and accent colour. No framework assumption, no package to install, nothing that requires access to the host site’s build pipeline — because there usually is not one.

  • 02

    Shadow DOM isolation

    The widget renders inside a shadow root with its own stylesheet. The host page’s CSS cannot reach in and reset a button; my CSS cannot reach out and restyle their headings.

  • 03

    Deferred load, no layout shift

    Nothing renders until idle or intent. The launcher is fixed-position and reserves no document space, so the host page’s cumulative layout shift is unchanged.

  • 04

    Answers with the page they came frommodel-backed

    Every response links the page it was drawn from. A visitor can check the claim in one click, and the business can see which page a wrong answer came out of.

  • 05

    Refusal as a designed pathmodel-backed

    Below the relevance threshold, the widget says it does not know and offers a person. Pricing, legal and warranty questions are routed to a human regardless of retrieval score.

  • 06

    Handover with the conversation attached

    WhatsApp deep link or a prefilled email, carrying the transcript so far. The visitor does not repeat themselves and the business does not start cold.

  • 07

    Degrades to a form

    If the model endpoint is unreachable, the widget shows the handover form instead of an error. An outage upstream looks like a contact form to a visitor.

  • 08

    Keyboard and screen reader support

    Focus moves into the panel on open and returns to the launcher on close, Escape closes, new messages are announced through a live region and never silently appended.

04User flow

How a person moves through it

The path from the trigger to the finished record, with each step attributed to whoever performs it — a person or the system.

  1. 01VisitorLands on the site; the launcher appears without the page moving.
  2. 02VisitorOpens the widget and asks a question in their own words.
  3. 03WidgetRetrieves the closest passages from the client’s own indexed pages.
  4. 04WidgetAnswers only from those passages and shows the source page beneath the answer.
  5. 05WidgetWhere relevance is below threshold, or the topic is pricing or legal, offers a person instead.
  6. 06VisitorTaps the handover; the conversation is carried into WhatsApp or an email draft.
  7. 07BusinessReceives the enquiry with the full exchange, not a one-line form submission.

05Architecture

How it is put together

The processing path first, then the layers it runs on, then the constraints that shaped both.

Path through the system

6 stages

  1. 01

    Embed

    A deferred loader script resolves the account config and attaches a launcher. It registers listeners and returns; the widget bundle is a separate request that has not happened yet.

  2. 02

    Hydrate on intent

    First click, or an idle callback after the page settles, fetches the widget bundle and mounts it into a shadow root. The launcher is interactive before the bundle exists, so an early click is queued, not dropped.

  3. 03

    Retrieve

    The question is embedded and matched against chunks built from the client’s own pages at index time. Top matches come back with their source URL and a similarity score.

  4. 04

    Ground or refuse

    Below the score threshold the model is never called — the refusal is cheaper, faster and safer than asking a model to be honest about not knowing.

  5. 05

    Answer

    The model sees only the retrieved passages and is instructed to answer from them alone. The source link is attached from the retrieval result, not from anything the model produced.

  6. 06

    Escalate

    Handover builds a WhatsApp deep link or mailto with the transcript, truncated to fit URL limits, oldest messages dropped first.

Layers

Host page
One deferred script tagData attributes for account and accentNo framework requirementNo CSS changes
Widget
Shadow rootScoped stylesheetFocus managementARIA live regionQueued pre-hydration clicks
Retrieval
Page chunking at index timeEmbeddings with source URLsSimilarity thresholdTopic rules that override score
Fallback
Handover formWhatsApp deep linkPrefilled mailtoTranscript truncation

Why it is shaped this way

  • The index is rebuilt from the client’s pages and not crawled per question. Live crawling would make every answer depend on their site being up and fast, and that is not a dependency worth adding.
  • The widget never writes to localStorage on the host origin without a reason; conversation state lives in memory for the page session, which also removes an entire consent conversation.
  • A strict content security policy on the host site is the most common integration failure. The loader is a single origin with no inline script, so a nonce is not required.
  • Answers are capped short. A widget that returns four paragraphs is a widget nobody reads, and length correlates with the model wandering off the retrieved passages.

06Technical decisions

The choices that mattered, and what each one cost

Every decision here was contested by a reasonable alternative. The trade-off column is the part usually left out.

01

Shadow DOM rather than an iframe

Why

An iframe isolates perfectly but cannot overlay the page cleanly, needs postMessage for every resize, and behaves badly on mobile keyboards. A shadow root gives isolation while staying in the host’s layout.

Trade-off

Styles have to ship inside the root, not through a stylesheet link, and a few host-level properties — font smoothing, reduced motion, colour scheme — still inherit and have to be handled explicitly.

02

Index the client’s pages at build time

Why

Answers stay fast and stay available even when their site is slow. It also makes the corpus reviewable: the business can see exactly what the widget is allowed to answer from.

Trade-off

Content changes are not reflected until the next index run, so a price change on their site can be stale in the widget. The mitigation is that pricing questions escalate to a person regardless.

03

Refuse below a similarity threshold instead of asking the model to hedge

Why

A model asked to say "I do not know" will still sometimes answer. A threshold check in code cannot be talked out of it, and it costs nothing to run.

Trade-off

The threshold is a blunt instrument and will occasionally refuse a question the corpus could have answered. For a customer-facing widget on someone else’s brand, that is the right direction to be wrong in.

04

Handover to WhatsApp, not a ticket system

Why

The businesses asking for this already run on WhatsApp. Introducing a helpdesk they would have to check daily would have quietly broken the whole flow.

Trade-off

No queue, no assignment, no SLA tracking — and no history beyond the owner’s phone. If volume grew, this is the first thing that would need replacing.

07Challenges

What was genuinely difficult

Not the setup work. These are the problems where the first implementation was wrong and had to be reconsidered.

01

The host site’s global CSS reset flattened every button and input inside the widget.

Approach

Moved from a plain div mount to a shadow root with an adopted stylesheet, then audited which properties still inherit through the boundary and set them explicitly at the root.

Outcome

The widget now looks the same regardless of the host stylesheet, and a later redesign of the host site required no changes on my side.

02

Visitors clicked the launcher before the widget bundle had loaded, and the first click did nothing.

Approach

The loader attaches its own listener immediately, records that intent, and replays it once the bundle mounts, with the launcher showing a loading state in the interim.

Outcome

An early click now opens the panel as soon as it is ready instead of being swallowed — the failure that would otherwise read as "the chat is broken".

03

On mobile, opening the keyboard collapsed the panel and scrolled the host page behind it.

Approach

Sized the panel against the visual viewport, not the layout viewport, locked host scroll while open without changing body position, and restored scroll offset on close.

Outcome

Typing on a phone no longer moves the page underneath, and closing the widget returns the visitor to exactly where they were.

04

Long conversations produced handover links that exceeded URL length limits and silently truncated mid-word.

Approach

Budgeted the transcript against a conservative length limit, dropping the oldest turns first, keeping the visitor’s most recent question intact, and marking that earlier context was omitted.

Outcome

The handover always arrives readable, with the newest and most relevant part of the exchange preserved.

08Business value

What it changes for the business

Stated qualitatively on purpose. Invented percentages are the easiest thing to put on a portfolio and the easiest thing to see through.

Operational effect

  • The routine questions get answered at the moment a visitor has them, not when the phone is next free.
  • Answers point at the business’s own pages, so a visitor can verify a claim and the business can trace a wrong one.
  • Questions that carry commercial or legal risk reach a person by design; nothing depends on luck.
  • Enquiries arrive with the conversation attached, so the first reply can be useful and not a request for clarification.
  • An outage in the model service looks like a contact form to a visitor, not like a broken website.

What would change at scale

  • Handover would need a real queue. WhatsApp works for one owner and stops working the moment two people are meant to share responsibility for replying.
  • The index would need incremental updates on content change, not a full rebuild, which mostly means the client’s CMS has to emit an event — usually the hardest part of that conversation.
  • Conversation logs would need a retention policy written down before volume makes it interesting, not after.

09Interface

The screens where the work happens

Dense operational views rather than dashboards. These are used for hours at a time, so the priorities are legibility, keyboard flow and state that is never ambiguous.

Widget panel

The visitor-facing conversation, with the source page shown under a grounded answer.

Refusal path

A question the corpus cannot support, declined and offered to a person — the behaviour for about a third of real conversations.

Retrieval trace

What the widget matched against and with what score — the only way to judge whether an answer was earned.

Handover

The prefilled message the business receives, with the transcript carried over.

The screen above is built in HTML and CSS for this case study. It reproduces the layout, states and vocabulary of the real build without exposing client data, which is why it exists rather than a screenshot. It is evidence of design and of the decisions behind it. It is not a photograph of a deployed system, and no part of it is a capture of anyone else’s product.

10Technologies

What it is built with

Chosen for the shape of the problem, not for novelty. Anything unusual is justified in the decisions section above.

Embed
  • Vanilla loader script
  • Shadow DOM
  • Adopted stylesheets
  • requestIdleCallback
Widget
  • TypeScript
  • Preact-sized runtime
  • CSS custom properties for theming
  • ARIA live regions
Retrieval
  • Page chunking
  • Embeddings
  • Similarity threshold
  • Source URL per chunk
Model
  • GPT-4o-mini
  • Retrieved-context-only prompting
  • Short answer cap
Handover
  • WhatsApp deep links
  • Prefilled mailto
  • Transcript budgeting

Implementation detail

  • Loader under three kilobytes that renders nothing and defers the widget bundle until idle or intent.
  • Shadow root with an adopted stylesheet, plus an explicit reset for the properties that still inherit across the boundary.
  • Pre-hydration click capture and replay, so the first interaction is never lost.
  • Similarity threshold enforced in code before the model is called, making refusal deterministic and not a matter of prompt compliance.
  • Topic rules that override retrieval score for pricing, legal and warranty questions.
  • Visual-viewport sizing and scroll locking for mobile keyboards, without repositioning the host body.
  • Transcript budgeting against URL length limits, dropping oldest turns first.
  • Handover form doubles as the failure state, so an unreachable model endpoint degrades and never errors.

11Technical preview

Structure, models and annotated excerpts

There is no repository link on this site. What is available instead is the module structure, the data model, the interface contracts and annotated excerpts written for this case study — enough for a technical reviewer to judge the engineering.

Technical preview

Implementation detail for this build

Selected implementation details are available for technical review: component structure, data models, architecture notes and sanitized code excerpts. Some client-specific source material stays private because of confidentiality. Enter the project access keyword if you have been given one — the same one works across every case study.

A presentation convenience, not authentication. This is a static site, so the keyword ships to your browser with the page and anyone reading the bundle can find it — worth saying plainly rather than dressing it up. Nothing confidential is stored behind it.

Inside this section

  • Module and folder structure for the build
  • Data models and the interface contracts between layers
  • 3 annotated implementation excerpts
  • Design notes covering the decisions the excerpts imply
  • The same keyword opens every case study on the site

For private client work, contact me for a walkthrough.

Next step

Need something along these lines?

Send the process, the constraints and the deadline. You will get an honest scope, an architecture sketch and a timeline before any commitment.

Email
surajk86808@gmail.com
Based in
Bengaluru, India
Working hours
IST (UTC+5:30)
Availability
Taking new engagements