← Blog

How to Build a Claude-Powered Support Widget

2026-10-31 · 5 min read · SubToAPI Team

A Claude-powered support widget is a chat interface embedded on your site or app that answers customer questions using Claude instead of (or alongside) a human agent. Building one requires three pieces: a frontend chat UI, a backend endpoint that talks to Claude, and a system prompt that constrains Claude to your product's context so it doesn't hallucinate answers about things it has no knowledge of.

The fastest path is: embed a lightweight chat widget on the frontend, proxy every message through your own backend (never call Claude directly from the browser), inject your help docs or FAQ into the system prompt, and stream the response back token-by-token so the widget feels responsive. Below is a working pattern you can adapt regardless of which backend language you use.

Architecture overview

Browser widget → Your backend endpoint → Claude API → stream back → Browser

Keep the API key server-side, always. If you put a Claude key in frontend JavaScript, anyone can open devtools, copy it, and run up your bill. Your backend is also where you enforce rate limits, log conversations, and decide when to escalate to a human.

A typical support widget backend does four things:

  1. Receives the user's message and conversation history
  2. Adds a system prompt with your product's support context
  3. Calls Claude (streaming for a responsive UI)
  4. Optionally uses tool calls to look up order status, ticket history, or docs

Step 1: Build the system prompt

Your system prompt is the single highest-leverage thing in this project. A generic "you are a helpful assistant" prompt produces generic, often wrong answers. Instead, feed it:

You are the support assistant for Acme Analytics.
Only answer questions about Acme's product, billing, and account settings.
If you don't know the answer or the question is about account-specific data
(refunds, invoices, account deletion), say so and tell the user you're
escalating to a human.
Never invent pricing, features, or policies not listed below.

[condensed docs here]

Step 2: Build the backend endpoint

Here's a minimal Node.js example using SubToAPI, which turns your Claude access into a plain HTTPS endpoint with an application API key (sub_live_...) — handy if you want streaming, tool use, and usage metadata without managing Anthropic infrastructure yourself.

// server.js
app.post("/api/support-chat", async (req, res) => {
  const { messages } = req.body; // [{role, content}, ...]

  const response = await fetch("https://api.subtoapi.app/v1/messages", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "claude-sonnet-4",
      max_tokens: 500,
      system: SUPPORT_SYSTEM_PROMPT,
      messages,
      stream: true,
    }),
  });

  res.setHeader("Content-Type", "text/event-stream");
  response.body.pipe(res); // forward the SSE stream to the browser
});

Setup like this takes a few minutes if you follow the quickstart guide — generate a key, drop it in an environment variable, and you're calling Claude through a normal REST endpoint. Full request/response shapes are documented at /docs/messages.

Step 3: Stream responses to the widget

Support widgets feel broken if the user stares at a blank bubble for 3-5 seconds. Streaming fixes this by rendering tokens as they arrive. On the frontend, consume the SSE stream and append tokens to the chat bubble:

const res = await fetch("/api/support-chat", {
  method: "POST",
  body: JSON.stringify({ messages: conversationHistory }),
});

const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value);
  // parse SSE chunks and append text deltas to the chat bubble DOM node
}

Streaming mechanics and event formats are covered in more depth in /docs/streaming if you want the full event structure instead of hand-rolling the parser.

Step 4: Add tool use for real support data

A widget that can only talk about generic docs is limited. Real support bots need to check order status, pull a user's plan, or search a knowledge base. This is where tool use comes in — you define functions Claude can call, and your backend executes them:

tools: [
  {
    name: "get_order_status",
    description: "Look up the status of a customer order by order ID",
    input_schema: {
      type: "object",
      properties: { order_id: { type: "string" } },
      required: ["order_id"],
    },
  },
]

When Claude decides it needs order data to answer, it returns a tool call instead of plain text. Your backend runs the actual database lookup, sends the result back in the next message, and Claude incorporates it into its reply. This pattern — model decides when to call out, your code executes, model finishes the answer — is detailed in /docs/tools.

Step 5: Handle escalation and conversation state

Decide upfront what triggers a handoff to a human: explicit requests ("talk to a person"), repeated failed answers, or sensitive topics like refunds. The cleanest implementation is a simple rule layered on top of Claude's output — check the system prompt's "I'm escalating" phrase, or have Claude return a structured flag you check in code.

Store conversation history per session (a database row keyed by session ID is enough for most widgets) and trim it before each request — Claude doesn't need the full chat history if it's grown long, just the last several exchanges plus a summary.

Step 6: Rate-limit and monitor usage

Support widgets are public-facing, which means they're exposed to abuse — people pasting huge blocks of text, bots hammering the endpoint, or just high legitimate volume during a product incident. Rate-limit per session/IP on your backend, cap max_tokens, and keep an eye on usage. If you're running this through a service that gives you per-key usage metadata out of the box, you can catch runaway sessions before they become a surprise bill — worth checking against your pricing plan limits regardless of provider.

Questions

Does the widget need to call Claude directly from the browser? No, and it shouldn't. Always proxy through your own backend so the API key stays server-side, you can enforce rate limits, and you can log conversations for debugging and quality review.

How do I stop the bot from answering questions outside my product scope? Constrain it in the system prompt with explicit boundaries and a fallback instruction ("if unsure, say so and escalate"). Testing with real past support tickets is the fastest way to find gaps before launch.

Can I add this without managing my own Anthropic infrastructure? Yes — tools like SubToAPI expose Claude as a standard HTTPS API with an application key, streaming, and tool use already wired up, so you focus on the widget and system prompt rather than infra. Start with a free trial at /signup.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →