How to Embed a Legal AI Chat Widget in Your Client Portal or Intranet

To embed a legal AI chatbot in a client portal or law firm website, you pick one of three builds: drop a vendor iframe in, paste a drop-in JS widget script, or build your own chat UI on top of an API behind a server-side proxy. The iframe ships in an afternoon. The proxy is the one that holds up when a client reads the answer and acts on it.

TL;DR

Three real ways to ship an embedded legal AI chat widget into a client portal, intranet, or law firm website: an iframe, a drop-in JS widget, or your own UI on top of an API. Iframes ship fastest and give you the least control.

The production answer for most firms is your own UI talking to a thin server-side proxy that hides API keys, enforces auth, rate-limits per user, and verifies answers before they reach a client's screen.

A legal AI chat widget on a client-facing surface needs five things: it has to be auth-scoped, grounded in your content, verified, disclaimed, and logged. A generic intake bot skips most of these.

Never let a model's raw output go out untagged: in a client-facing context, that is a malpractice surface, not a UX detail.

Part of our MCP and developer guide series.

For related coverage, see Pulling Legal Research Into Your Internal App: The Vaquill AI Legal API, Legal API in 2026: What It Is, What It Returns, and How to Pick One, and Building an Internal Legal Copilot for an In-House Team.

Quick check

Per the post, what five things does a legal AI chat widget on a client-facing surface need to be?

Two real use cases, and why they end up different

Two patterns show up over and over when firms or in-house teams ask about embedding legal AI into something they already own.

The law firm client portal. A small or mid-size firm has a portal (Clio, MyCase, PracticePanther, or a homegrown one) where clients log in for case status, engagement letters, and invoices. Partners want a chat box so a client can ask "what stage is my divorce in" or "what does this indemnification clause mean in plain English".

Answers go directly to a non-lawyer who retained the firm. Anything that looks like advice, or gets a fact wrong, lands on the firm.

The in-house legal team's intranet. A 200-person product company has an in-house legal team of four. Sales and procurement send "is this redline acceptable" questions all day.

The team wants a Slack-style assistant that answers "what's our standard liability cap in MSAs" or "does this NDA conflict with our template". The audience is internal employees deciding whether to escalate or just bounce the agreement back to the vendor.

The two look similar from outside (chat bubble, thread, sources). Inside they are different.

The client portal has UPL exposure, privilege questions, and a higher verification bar. The intranet bot is closer to a productivity tool, but still has confidentiality issues (counterparty NDAs do not belong in a public model). Same building blocks, different knobs.

The three architectures

"We want to embed a legal AI chat widget" almost always means one of three things.

ArchitectureTime to shipAuth controlBrandingAudit trail in your systemsRight for
Iframe embedHoursLowVendor chromeNoA 48-hour pilot
Drop-in JS widgetDaysMedium (JWT/HMAC)Skinning onlyPartialInternal intranet bot
API + your own UIWeeksFullNativeYesAnything a client sees

1. Iframe embed

The vendor hosts the chat UI at a URL and you drop it into a page with an <iframe> tag. Maybe you can pass a theme color and a logo. That is most of what you get.

<iframe
  src="https://chat.example-vendor.com/embed?tenant=acme-law"
  style="width:100%;height:600px;border:0"
  allow="clipboard-write"
></iframe>

What you give up: styling, deep auth integration, control over what the assistant can access, and the ability to log the conversation in your own systems. Cross-domain cookies and sandboxing also mean privacy extensions will block the iframe on a non-trivial slice of users.

What you get: a working chat widget in an afternoon.

Use this when you are validating whether anyone in the portal will use a chat box at all. It is not the answer for production in a firm that takes its brand seriously.

2. Drop-in JS widget

The vendor ships a script tag. You add it to your portal layout, configure a few options on window, and a floating chat bubble appears.

<script>
  window.LegalAIConfig = {
    tenant: "acme-law",
    theme: "light",
    user: { id: "client_4821", displayName: "J. Patel" }
  };
</script>
<script src="https://cdn.example-vendor.com/widget.js" async></script>

You get more skinning, a real bubble UI, often event hooks (onMessage, onOpen). The vendor still hosts the backend, which means the API key, prompts, and retrieval logic all live on their side. You usually identify the end user through a JWT or HMAC-signed payload the widget exchanges with the vendor.

Right answer for an in-house legal team that wants something nice on the intranet in a week and is fine with the assistant looking like a third-party tool. Wrong answer when the assistant needs to ground on your firm's matter data, or when you want every conversation in your own warehouse without a vendor re-export.

3. API + your own UI

You build the chat interface in your portal's component library. On every message, the client hits your own server.

Your server holds the secret API key, calls the legal data provider of your choice (a hosted statutes API, an open-source model behind your own retrieval layer, whatever), and streams the response back. Auth, rate-limiting, logging, audit trail, verification, and any "is this safe to show the client" gate live in your own code.

This takes real engineering. It is also the only architecture that lets you ship a client-portal widget that holds up under scrutiny. When a partner has to explain to a malpractice carrier how the assistant answered a given question, "we owned every layer between the client and the model" is the answer that ends the conversation quickly.

The rest of this post focuses on this third pattern. The first two are mostly configuration.

Auth patterns: where most embedded widgets get this wrong

Before any code, the rule that matters: API keys never live in browser JavaScript. Not in a NEXT_PUBLIC_ env var, not in a build constant, not behind any kind of obfuscation.

Once the key is on the client, anyone who opens DevTools can scrape it and run up your bill.

Three patterns that work.

Server-side proxy with the user's session cookie. The browser hits /api/legal-ask on your own domain. Your server checks the session, decides what they can ask about (this client, this matter, no others), and forwards the request to the upstream provider using a secret in the server's env. Right default for almost every client portal.

Short-lived signed tokens. Your server mints a JWT scoped to one user and one set of permissions, valid for ten minutes. The browser sends that token to the upstream directly.

Useful when you want to skip a hop, or when the upstream supports streaming and you don't want to proxy bytes. Token has to be narrow: one user, one tenant, short expiry.

BYOK (bring your own key). The end user pastes their own provider key into a settings page and the widget calls the upstream with it. Fine inside a developer tool ("paste your OpenAI key, I'll use it"). Wrong model for a client portal, where the firm is the entity contracting with the vendor and the client should not be holding keys.

In practice, almost every client-portal widget wants the first pattern. The intranet bot can sometimes get away with the second.

A minimal Next.js proxy you can actually ship

Loading diagram...

Smallest version of pattern three: a Next.js route handler that takes a question from the browser, checks the user, calls a US statutes search API on the server, and returns the answer.

The example points at the Vaquill AI statutes endpoint; the shape applies to any backend.

// app/api/legal-ask/route.ts
import { NextRequest } from "next/server";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { rateLimit } from "@/lib/rate-limit";

export const runtime = "nodejs";
const UPSTREAM = "https://api.vaquill.ai/api/v1/statutes/search";

export async function POST(req: NextRequest) {
  // 1. Auth: who is asking?
  const session = await getServerSession(authOptions);
  if (!session?.user?.id) return new Response("unauthorized", { status: 401 });

  // 2. Per-user rate limit: defend the upstream key and your wallet
  const ok = await rateLimit(`legal-ask:${session.user.id}`, 20, "1 m");
  if (!ok) return new Response("slow down", { status: 429 });

  // 3. Validate input
  const body = await req.json().catch(() => null);
  const query: string = body?.query?.toString().trim() ?? "";
  if (query.length < 3 || query.length > 500) {
    return new Response("bad query", { status: 400 });
  }

  // 4. Server-side call with the secret API key
  const upstream = await fetch(UPSTREAM, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.VAQUILL_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ query, corpusType: "USC", limit: 5 }),
  });

  if (!upstream.ok) return new Response("upstream error", { status: 502 });
  return new Response(upstream.body, {
    headers: { "Content-Type": "application/json" },
  });
}

The chat component on the client calls /api/legal-ask with fetch. No keys touch the browser. CORS is gone because the request stays on the same origin.

You can add request-ID logging, redact the question before it hits your warehouse, and feature-flag the whole thing without touching the upstream.

Things that look optional and aren't:

  • Rate-limit per authenticated user, not per IP. Corporate NAT means every employee shares one IP, so any per-IP limit will be too tight or too loose.
  • Cap input length on the server. The 500-character cap above is arbitrary, pick your own. The moment you do not cap, somebody pastes a 200-page deposition.
  • Stream when you can. Pipe the upstream's stream through. Users tolerate a slow answer if they see it forming. They reload on a 12-second white screen.
  • Log enough to debug, not enough to leak. Request ID, user ID, upstream status, latency, token count. Not the question by default. Behind a per-tenant flag for support.

For how a legal RAG backend wires the retrieval side together, the how AI legal research works under the hood post walks through it.

Grounding the widget on your own content

A bare model answering legal questions in a client portal is the failure mode. The widget should answer from sources you control: your firm's FAQ, the matter file, your engagement-letter language, and a statutes corpus for anything jurisdictional. That is the difference between an intake toy and a tool a partner will sign off on.

The mechanics are retrieval. On each question, your proxy pulls the relevant chunks (firm policy docs, the client's own matter records, statute sections), hands them to the model as context, and instructs it to answer only from what it was given and cite the source.

Here is a worked example. A client in a portal asks: "What does the indemnification clause in my engagement letter mean?" A grounded widget retrieves the actual clause text from the matter file, then answers:

Your engagement letter (Section 7, "Indemnification") says you agree to cover the firm's costs if a third party sues over information you provided that turns out to be false. It does not cover the firm's own mistakes. This is general information about your document, not legal advice. [View clause] [Ask your lawyer]

Notice what it did not do: invent a clause that is not in the file, or opine on whether the term is fair. It quoted the client's own document and routed the judgment call to a human. For the pattern of answering questions directly off matter documents, see searching your matter files conversationally.

Two grounding rules that keep the widget safe:

  • Scope retrieval to the requesting user. A client should only ever retrieve their own matter. Enforce this in the proxy with the session identity, never trust a tenant ID sent from the browser.
  • Make citations checkable. Every answer should carry a source the user (or a reviewing partner) can open. An answer with no openable source is the one that ends up in a malpractice file.

The pitfalls that show up in week two

The proxy works. The widget renders. Then the next set of problems shows up.

CORS. The server-side proxy makes this disappear, which is most of why we recommend it. Skip the proxy and every provider's CORS policy becomes your problem, with errors that surface only on specific browser versions in specific Slack-embedded previews.

Rate limits and bursts. A portal where 12 people log in at 9 a.m. spikes to 50 concurrent requests in two seconds. Upstreams throttle. Add a tiny queue with a per-tenant ceiling and surface the wait state in the UI instead of returning a 429.

Hallucination risk. This is the one that should keep partners up at night. Every credible study of legal AI accuracy (the Stanford HAI 2024 "Hallucination-Free?" study is the one to read) finds that even purpose-built legal RAG tools fabricate at meaningful rates.

Inside a firm, an associate catches the bad cite. Inside a client portal, the client reads it and acts.

Any answer headed to a client surface needs a verification pass: a check that cited sections exist in the corpus, plus a confidence floor below which nothing renders. The longer story on skipping this step is in our legal AI hallucinations and sanctions post.

Privilege and confidentiality. Anything the user types is potentially privileged. If your upstream trains on inputs, you have blown privilege for every conversation. Read the data-processing terms before you ship, and trace exactly where your legal AI data actually goes. Our security and data policies page lays out the bar to hold a provider to.

Disclaimers that hold up. Every message back to a client should be tagged "not legal advice" and "informational only" in the body, not in a tiny footer the user can dismiss. It is not a UX nicety. It is what the malpractice carrier asks about.

UPL and the appearance of advice

If the widget is on a client portal and the client is interacting with a system the firm put there, the firm is on the hook for what the system says. UPL rules differ by state, but the common thread is that giving legal advice for compensation by anyone (or anything) not licensed in that jurisdiction is a problem.

A widget that answers "what does this clause in my engagement letter mean" with a confident, jurisdiction-aware answer is functionally giving advice. A clear "AI assistant" label does not save the firm if the average client reasonably believes they're getting guidance from it.

Practical guardrails:

  • Scope the assistant. A widget that only answers "what is the status of my matter" from firm data is much lower risk than one that answers any legal question.
  • Refuse and route. System-prompt the assistant to refuse anything that calls for advice and surface a "send to your lawyer" button instead.
  • Log to the matter file. Every conversation should land in the matter's record like an email or call log. If the client later disputes what they were told, you need the transcript.
  • Disclose in the engagement letter. "The firm provides an AI assistant on the client portal. It provides general information only, not legal advice. Communications may be reviewed by lawyers and staff for quality and accuracy."

ABA Formal Opinion 512 (walkthrough) is the current backdrop. Competence, confidentiality, supervision, and candor do not pause at the edge of an AI tool.

For the in-house intranet case the calculus is gentler. UPL is not the live concern, confidentiality and trade-secret exposure are.

The widget should still refuse counterparty documents marked confidential, log every query for audit, and never auto-share an answer outside the tenant.

A short build checklist

Rough order of operations:

  1. Decide who the user is, client or employee. Architecture, disclaimers, and verification bar all flex on this.
  2. Pick the architecture: iframe for a 48-hour pilot, drop-in JS for an internal tool, API + your own UI for anything client-facing.
  3. Build the proxy first. Auth, rate limit, input validation, key on the server. Stub the upstream until the proxy is solid.
  4. Pick a grounded backend, not a raw LLM. The widget is only as safe as the system behind it.
  5. Add a verification gate before any answer renders. Sourced only. Refuse on low confidence.
  6. Disclaim every response. "Informational only, not legal advice."
  7. Log every conversation to the matter file or the legal team's audit trail.
  8. Run it past the malpractice carrier before launch.

None of it is glamorous. All of it is the difference between a widget that helps the firm and a widget that becomes the firm's worst week.

Where the statutes API fits

Vaquill AI's public API covers US statutes and legislation: the U.S. Code, the CFR, and all 52 state and territory codes. That is the slice you plug into a widget when the question is statutory.

Search results come back with section text and source metadata, so the widget can render them inline and a partner can confirm the system did not invent a citation. Richer research surfaces (case law lookups, citation networks, AI Q&A, drafting) are in-product features of the workbench, not public API endpoints.

Vaquill AI US statutes search returning section text and source metadata

FAQ

Three options. Drop a vendor iframe into a page (fastest, least control), paste a drop-in JS script tag that renders a chat bubble (more skinning, vendor still holds the backend), or build your own chat UI that calls your server, which then calls the AI provider. For anything a client reads and acts on, the third option is the only one that lets you control auth, grounding, and the disclaimer.

Yes, with guardrails. ABA Formal Opinion 512 (2024) confirms lawyers can use generative AI as long as competence, confidentiality, supervision, and candor are maintained. The widget must clearly identify itself as AI, avoid giving legal advice, and route anything that calls for judgment to a human. The firm stays responsible for what the system says.

What disclaimers does a law firm chatbot need?

Tag every response "informational only, not legal advice" in the message body, not buried in a footer the user can dismiss. Disclose the assistant in the engagement letter, and state that conversations may be reviewed by lawyers and staff. A clear "AI assistant" label does not protect the firm if a client reasonably believes they are getting legal guidance.

Does a chatbot break attorney-client privilege?

It can, if the AI provider trains on user inputs. Anything a client types is potentially privileged, so use a provider that contractually does not train on your data and read the data-processing terms first. Scope retrieval so a client only ever sees their own matter, and log every conversation to the matter file.

Ground it on sources you control and gate the output. Retrieve from your firm docs, the matter file, and a statutes corpus, instruct the model to answer only from what it was given, then run a verification pass that confirms cited sections exist and refuses to render below a confidence floor. Even purpose-built legal RAG tools fabricate at meaningful rates, per the Stanford HAI 2024 study.

Can I add an AI chatbot without code?

For an intake bot, yes. No-code platforms give you a script tag and a hosted backend in an afternoon. But a no-code widget cannot ground on your matter data, log to your own systems, or enforce per-client auth. The moment the bot needs to answer about a specific client's case, you need a real proxy and some engineering.

What is the difference between an iframe and a JS widget embed?

An iframe loads the vendor's whole chat page inside a frame on your site: fast, but you get vendor branding and no access to the conversation. A JS widget injects a chat bubble you can skin and hook into with events, while the vendor still hosts the API key and retrieval logic. Neither gives you a server-side audit trail or content grounding; only your own UI on an API does.

Build the widget you would trust your own client to use

If you are putting an AI box on a screen a client looks at, the bar is higher than "it returned an answer." It has to be auth-scoped, grounded, verified, disclaimed, and logged.

The choices above decide whether you ship a widget the firm is proud of or one it quietly hides six weeks in.

To back the assistant with grounded US statutes and AI-checked answers, see the API or start at app.vaquill.ai, or email contact@vaquill.ai.

Legal AI that reads your documents and knows the law.
Ask a legal question, review a contract, or search thousands of your files. Every answer shows where it came from. 7-day free trial, no card.
19 min read

New legal AI guides, weekly.

Priyansh Khodiyar

Priyansh Khodiyar

Co-Founder & CTO

Priyansh leads engineering and AI at Vaquill, from the matter workbench to drafting, document comparison, document matrix, and citation-verified research.