nivq
This install section is for Enterprise / on-prem customers who self-host nivq.

Chat widget (embed)

Embed the nivq chat experience on any site with one tag — a framework-agnostic <nivq-chat> Web Component, a small token broker so your secret never reaches the browser, theming, and configuration.

You can drop the nivq chat experience onto any website or app with a single tag. The widget is a framework-agnostic Web Component (<nivq-chat>) — it works the same in plain HTML, React, Angular, or Vue — and renders inside a Shadow DOM, so its styles never leak into your page and your CSS never breaks it.

HTML
<script type="module" src="https://cdn.jsdelivr.net/npm/nivorbit-chat-widget@1/dist/nivq-chat.js"></script>
<nivq-chat
  agent-id="YOUR_AGENT_ID"
  api-base-url="https://api.example.com"
  token-endpoint="/nivq-token">
</nivq-chat>

That token-endpoint is the one piece you host yourself. Here's why.

How auth works — read this first

An nivq API client is an OAuth2 credential pair (clientId + clientSecret) used with the client_credentials grant:

clientId + clientSecret    POST /oauth2/token    short-lived access token (~1h)
access token (Bearer)      POST /v1/agents/{agentId}/conversations   (chat)

The clientSecret is a long-lived root credential — anyone who reads it can mint tokens and run up your usage. So it must never reach the browser. The widget only ever holds a short-lived access token, which it fetches from a small endpoint on your backend (the "token broker"). The secret stays on your server.

Browser (widget) ──POST──►  your /nivq-token  ──client_credentials──►  NivQ /oauth2/token
                                 
       └──── { access_token } ◄────┘     (the secret is never returned to the browser)

Never ship the secret to the browser

Don't put clientSecret in the widget, in client-side JS, or in a public config. Keep it on your backend and hand the widget only the short-lived token.

1. Create an API client

In nivq, create an API client (the screen that issues a clientId + clientSecret). On that key, list every web origin the widget will be embedded on under Allowed origins — e.g. https://app.acme.com — since the widget streams chat straight from the browser to your API (see Allow your embedding origin below).

2. Run a token broker

The broker is one endpoint on your backend. It authenticates your end-user with your own session, exchanges the secret for a token, and returns just { access_token, expires_in }. The widget caches the token and refreshes it before it expires.

JS
// Node / Express
import express from "express";
const app = express();

app.post("/nivq-token", async (req, res) => {
  // 1. Authenticate YOUR user (session/cookie/JWT). Reject if not signed in.
  // if (!req.user) return res.sendStatus(401);

  // 2. Exchange the secret for a short-lived token.
  const r = await fetch(`${process.env.NIVQ_API_BASE}/oauth2/token`, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "client_credentials",
      client_id: process.env.NIVQ_CLIENT_ID,
      client_secret: process.env.NIVQ_CLIENT_SECRET, // stays server-side
    }),
  });
  if (!r.ok) return res.sendStatus(502);
  const { access_token, expires_in } = await r.json();

  // 3. Return ONLY the token — never the secret.
  res.json({ access_token, expires_in });
});
JAVA
// Spring Boot
@RestController
class NivqTokenBroker {

  private final RestClient client = RestClient.create();

  @Value("${nivq.api-base}")      String apiBase;
  @Value("${nivq.client-id}")     String clientId;
  @Value("${nivq.client-secret}") String clientSecret; // stays server-side

  @PostMapping("/nivq-token")
  Map<String, Object> token(/* inject your authenticated principal here */) {
    var form = new LinkedMultiValueMap<String, String>();
    form.add("grant_type", "client_credentials");
    form.add("client_id", clientId);
    form.add("client_secret", clientSecret);

    var resp = client.post()
        .uri(apiBase + "/oauth2/token")
        .contentType(MediaType.APPLICATION_FORM_URLENCODED)
        .body(form)
        .retrieve()
        .body(Map.class);

    // Return only the token + expiry — never the secret.
    return Map.of("access_token", resp.get("access_token"),
                  "expires_in",   resp.get("expires_in"));
  }
}

3. Embed the widget

Plain HTML / any site

HTML
<script type="module" src="https://cdn.jsdelivr.net/npm/nivorbit-chat-widget@1/dist/nivq-chat.js"></script>
<nivq-chat agent-id="" api-base-url="https://api.example.com" token-endpoint="/nivq-token"></nivq-chat>

React

For bundled apps, install from npm:

Shell
npm i nivorbit-chat-widget
TSX
import "nivorbit-chat-widget"; // registers <nivq-chat>; ships its own types

export function Support() {
  return (
    <nivq-chat
      agent-id=""
      api-base-url="https://api.example.com"
      token-endpoint="/nivq-token"
    />
  );
}

The package ships type definitions, so the tag is typed out of the box. On React 19's strict JSX, add a one-line module augmentation if your editor flags it:

TS
declare module "react" {
  namespace JSX {
    interface IntrinsicElements {
      "nivq-chat": any;
    }
  }
}

Angular

TS
// app.module.ts — allow custom elements
import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })
export class AppModule {}
HTML
<nivq-chat agent-id="" api-base-url="https://api.example.com" token-endpoint="/nivq-token"></nivq-chat>

Configuration

AttributeRequiredDefaultDescription
agent-idThe nivq agent to chat with
token-endpointYour token broker URL
api-base-urlnivq chat API base (your deployment)
modefabfab (floating launcher) or inline
targetInline only: CSS selector of the container to mount into
positionbottom-rightbottom-right or bottom-left (FAB)
themeautolight, dark, or auto (follows OS)
localeautotr, en, or auto (browser language)
primary-colorNivQ brandHex (#6d28d9) or HSL triplet (265 70% 50%)
accent-colorNivQ brandHex or HSL triplet
radius0.625remCorner radius (any CSS length)
logo-urlNivQ markReplace the brand logo
brand-nameNivQHeader title / logo alt text
launcher-labelText next to the FAB icon
greetingCustom empty-state heading
placeholderlocalizedInput placeholder text
external-user-idDistinguishes your end-user (analytics / isolation)
persistfalsePersist the conversation in localStorage
start-openfalseFAB only: open the panel on load

You can also drive it from JavaScript:

JS
const el = document.querySelector("nivq-chat");
el.configure({ primaryColor: "#6d28d9", brandName: "Acme Assistant", mode: "inline" });

Theming

Defaults are the nivq brand. Override the key tokens and the rest derive from them:

HTML
<nivq-chat  primary-color="#6d28d9" accent-color="#f59e0b" radius="14px"
  logo-url="https://acme.com/logo.svg" brand-name="Acme Assistant"></nivq-chat>

Allow your embedding origin

The widget streams chat directly from the browser to your api-base-url (the broker only mints tokens), so your API must allow your site's origin. Set it on the API client: under Allowed origins, list every origin the widget is embedded on — scheme://host[:port], no path or wildcard, e.g. https://app.acme.com. An empty list disables browser embedding for that key (server-to-server only). Changes take effect within a minute.

On-prem

Point api-base-url at your own nivq deployment; the same per-key Allowed origins apply. If you'd rather manage embedding domains centrally, the deployment-wide CORS_ALLOWED_ORIGIN_PATTERNS setting does the same — see Configuration.

Privacy

One API client is shared across all end-users of your integration, and conversation history is scoped to the client — not to an individual person. The widget therefore keeps each session local (in memory, or localStorage when persist is on) and does not load shared history. Use external-user-id to distinguish your end-users on the nivq side.