ViksaAIDocs
Start now

Web Chat Widget

Embed your ViksaAI agents on any HTTPS website. The widget talks directly to volt-engine-service — no webhook on your site.

Architecture#

The Web Chat Widget is a first-party channel: the visitor's browser calls https://api.viksaai.com/v1/widget over HTTPS. Replies stream back via Server-Sent Events (SSE). Routing uses your widget ID (ww_…).

Public API reference#

EndpointMethodAuthentication
https://api.viksaai.com/v1/widget/{widget_id}/configGETBrowser Origin (allowlist)
https://api.viksaai.com/v1/widget/{widget_id}/sessionPOSTBrowser Origin (allowlist)
https://api.viksaai.com/v1/widget/{widget_id}/visitor-tokenPOSTAuthorization: Bearer {widget_secret} (server-to-server only)
https://api.viksaai.com/v1/widget/{widget_id}/historyGETOrigin + Authorization: Bearer {session_jwt}
https://api.viksaai.com/v1/widget/{widget_id}/messagesPOST (SSE)Origin + session JWT; optional X-Visitor-Token

All browser endpoints require a valid Origin header matching your allowlist (Referer is not accepted). Error responses use uniform 404 widget_not_found where appropriate to prevent enumeration.

GET /config — Returns appearance (title, colors, logo). No auth beyond origin.

config-response.jsonjson
{
  "widget_id": "ww_your_widget_id",
  "appearance": {
    "title": "Chat with us",
    "primary_color": "#d4e633",
    "position": "bottom-right",
    "greeting": "Hi! How can we help?",
    "placeholder": "Type a message…",
    "logo_url": "https://cdn.example.com/logo.png"
  }
}

POST /session — Creates a chat session. Call from the browser.

session-request.shsh
curl -s -X POST "https://api.viksaai.com/v1/widget/ww_your_widget_id/session" \
  -H "Origin: https://app.example.com" \
  -H "Content-Type: application/json" \
  -d '{}'
session-response.jsonjson
{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "token": "<session-jwt>",
  "expires_in": 14400
}

POST /visitor-token — Mint a visitor JWT from your backend (recommended). Authenticate with your widget secret (server-to-server only).

visitor-token-request.shsh
curl -s -X POST "https://api.viksaai.com/v1/widget/ww_your_widget_id/visitor-token" \
  -H "Authorization: Bearer $VIKSA_WIDGET_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "session_id": "550e8400-e29b-41d4-a716-446655440000",
    "email": "[email protected]",
    "name": "Jane Doe",
    "origin": "https://app.example.com"
  }'
visitor-token-response.jsonjson
{
  "visitor_token": "<visitor-jwt>",
  "expires_in": 900
}

GET /history — Requires session Bearer JWT.

history-request.shsh
curl -s "https://api.viksaai.com/v1/widget/ww_your_widget_id/history" \
  -H "Origin: https://app.example.com" \
  -H "Authorization: Bearer <session-jwt>"

POST /messages — Sends a message; response is SSE. Include optional visitor JWT for authenticated users.

messages-request.shsh
curl -N -X POST "https://api.viksaai.com/v1/widget/ww_your_widget_id/messages" \
  -H "Origin: https://app.example.com" \
  -H "Authorization: Bearer <session-jwt>" \
  -H "X-Visitor-Token: <visitor-jwt>" \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello", "client_msg_id": "msg-001"}'
sse-stream.txttxt
data: {"type":"phase","phase":"turn_started"}

data: {"type":"summary_delta","text":"Hello"}

data: {"type":"summary_delta","text":"! How can I help?"}

data: {"type":"message","message":"Hello! How can I help?"}

data: {"type":"done","success":true}

JWT claims#

Both token types use HS256. HS256 with key sha256(widget_secret) as hex string. Same algorithm for self-signing or verification. Retrieve your widget secret from Channel Hub → Web Chat Widget → Connection → Reveal secret (project admin only). Store it in an environment variable on your server — never in the browser.

ClaimSession JWTVisitor JWT
typwidget_sessionwidget_visitor
widWidget IDWidget ID
sidSession UUIDMust match session JWT sid
orgHTTPS originMust match browser Origin
emailUser email (access grant lookup)
nameOptional display name
exp~4h default~15 min default

Access control#

Web Chat uses channel grants only — not your Viksa project RBAC. Configure grants under Channel Hub → Web Chat Widget → Access.

Visitor typeIdentity sentGrant type
Logged-in userVisitor JWT emailEmail grant (e.g. [email protected] → agent)
AnonymousSession UUID onlySession ID grant or wildcard *
Public widgetNo visitor JWT + * grantAll agents (if you allow it)

Optional allowed visitor email domains (Widget tab) restrict which emails may appear in visitor JWTs (e.g. example.com only).

  1. 1

    User logs in on your site

    Your normal auth (OAuth, session cookie, etc.) stays on your backend. Viksa never sees it.

  2. 2

    Browser creates a widget session

    POST /session from the browser (Origin header sent automatically). Save session_id and session JWT.

  3. 3

    Your backend mints a visitor JWT

    Call POST /visitor-token with the widget secret, or sign the JWT yourself (see below). Pass the logged-in user's email and the same session_id.

  4. 4

    Browser loads the widget with both tokens

    Set window.ViksaChatConfig = { visitorToken } and pre-seed session storage, then load viksa-chat.js.

authenticated-embed.htmlhtml
<script>
(async function () {
  const widgetId = "ww_your_widget_id";
  const apiBase = "https://api.viksaai.com/v1/widget";
  const origin = window.location.origin;

  // 1) Create widget session (browser → Viksa)
  const sess = await fetch(apiBase + "/" + widgetId + "/session", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
  }).then((r) => r.json());

  // 2) Ask YOUR backend for a visitor JWT (uses your app login + widget secret)
  const { visitorToken } = await fetch("/api/viksa/visitor-token", {
    method: "POST",
    credentials: "include",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ session_id: sess.session_id, origin }),
  }).then((r) => r.json());

  // 3) Pre-seed session + config before widget script runs
  sessionStorage.setItem(
    "viksa_widget_" + widgetId + "_session",
    JSON.stringify({ session_id: sess.session_id, token: sess.token }),
  );
  window.ViksaChatConfig = { visitorToken };

  // 4) Load widget
  const s = document.createElement("script");
  s.src = "https://app.viksaai.com/widget/v1/viksa-chat.js";
  s.integrity = "sha384-Zp6RCQUzDfvAlbVwz41y1IvQrigyeCDOaHgUFVMcb3DimIaSSyi2gPjTQGOIJxDC";
  s.crossOrigin = "anonymous";
  s.async = true;
  s.setAttribute("data-widget-id", widgetId);
  s.setAttribute("data-api-base", apiBase);
  document.body.appendChild(s);
})();
</script>

Your backend: mint visitor token#

Expose an endpoint (e.g. POST /api/viksa/visitor-token) that requires your app's user session, then calls Viksa or signs the JWT locally.

Option A — Viksa mint API (simplest)

your_backend_mint.pypy
import os
import httpx

VIKSA_WIDGET_SECRET = os.environ["VIKSA_WIDGET_SECRET"]
WIDGET_ID = os.environ["VIKSA_WIDGET_ID"]
API_BASE = "https://api.viksaai.com/v1/widget"

def mint_visitor_token_for_user(*, session_id: str, origin: str, email: str, name: str | None):
    """Call after your auth middleware validates the logged-in user."""
    r = httpx.post(
        f"{API_BASE}/{WIDGET_ID}/visitor-token",
        headers={"Authorization": f"Bearer {VIKSA_WIDGET_SECRET}"},
        json={
            "session_id": session_id,
            "origin": origin,
            "email": email,
            "name": name,
        },
        timeout=10.0,
    )
    r.raise_for_status()
    return r.json()["visitor_token"]

Option B — Self-sign JWT (same algorithm Viksa uses)

self_sign_visitor_jwt.pypy
import hashlib, secrets, time
import jwt

def mint_visitor_token(*, widget_id, widget_secret, session_id, origin, email, name=None):
    key = hashlib.sha256(widget_secret.encode()).hexdigest()
    now = int(time.time())
    payload = {
        "typ": "widget_visitor",
        "wid": widget_id,
        "sid": session_id,
        "org": origin.rstrip("/").lower(),
        "email": email.strip().lower(),
        "iat": now,
        "nbf": now,
        "exp": now + 900,
        "jti": secrets.token_hex(12),
    }
    if name:
        payload["name"] = name[:120]
    return jwt.encode(payload, key, algorithm="HS256")
express-proxy.jsjs
// Example: Express route proxying to Viksa mint API
app.post("/api/viksa/visitor-token", requireUserSession, async (req, res) => {
  const { session_id, origin } = req.body;
  const user = req.user; // from YOUR auth

  const viksa = await fetch(`https://api.viksaai.com/v1/widget/${process.env.VIKSA_WIDGET_ID}/visitor-token`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.VIKSA_WIDGET_SECRET}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      session_id,
      origin,
      email: user.email,
      name: user.displayName,
    }),
  });

  if (!viksa.ok) return res.status(viksa.status).end();
  const { visitor_token } = await viksa.json();
  res.json({ visitorToken: visitor_token });
});

Basic embed (anonymous)#

embed-anonymous.htmlhtml
<!-- Anonymous visitors — grant by session UUID or * in Access tab -->
<script
  src="https://app.viksaai.com/widget/v1/viksa-chat.js"
  integrity="sha384-Zp6RCQUzDfvAlbVwz41y1IvQrigyeCDOaHgUFVMcb3DimIaSSyi2gPjTQGOIJxDC"
  crossorigin="anonymous"
  data-widget-id="ww_your_widget_id"
  data-api-base="https://api.viksaai.com/v1/widget"
  async
></script>

For logged-in users, do not use a static placeholder JWT. Follow the authenticated flow above.

Setup in Channel Hub#

  1. 1

    Connect the widget

    Volt → Channels → Web Chat Widget. Add HTTPS origins, appearance, optional logo and email domains.

  2. 2

    Copy credentials

    Connection tab: copy Widget ID, click Reveal secret for server-side minting. Store VIKSA_WIDGET_SECRET in your backend env.

  3. 3

    Configure access grants

    Access tab: add email grants for logged-in users or * for public widgets.

  4. 4

    Embed and test

    Use the Embed tab snippet or authenticated flow above. Test with the in-dashboard Test tab (impersonate email/session).

Security controls#

  • HTTPS origins only — exact match; browser Origin header required
  • Widget secret never in browser; visitor mint API is server-to-server only
  • Session and visitor JWTs bound to origin + session
  • Web Chat access is grant-only (no project RBAC merge)
  • Rate limits on session, config, history, messages, and visitor mint
  • One active turn per session; idempotency after access check
  • Optional allowed visitor email domains
  • Subresource Integrity on embed script; Shadow DOM UI

See also: Channel Hub security.