Chatbot widget embedding

Embed the Nerva chatbot widget on your website with one script tag — no JavaScript framework required.

The Nerva chatbot widget is a single JavaScript bundle that mounts a chatbot launcher on your site. It runs entirely inside a Shadow DOM, so its styles never collide with yours.

Quick install

Paste this into your site's <head> (or anywhere in the body — anywhere works):

<script
  src="https://cdn.ctpl.ai/widget/chatbot-widget.js"
  data-agent-id="agent_xxxxxxxxx"
  defer
></script>

Replace agent_xxxxxxxxx with your chat agent's ID, which you can copy from Dashboard → Agents → your chat agent → Embed tab.

That's it. A floating chat launcher appears in the bottom-right of every page. Visitors click to open and start chatting.

Configuration via data- attributes

The auto-init script picks up these attributes:

AttributeDefaultDescription
data-agent-id(required)The chat agent ID.
data-auto-openfalseOpen the widget immediately on page load.
data-positionbottom-rightbottom-right, bottom-left, top-right, or top-left.
data-idle-timeout-ms(agent default)Override the agent's idle timeout for this embed.
data-idle-timeout-message(agent default)Custom message shown when the session times out.

Example with options:

<script
  src="https://cdn.ctpl.ai/widget/chatbot-widget.js"
  data-agent-id="agent_abc123"
  data-position="bottom-left"
  data-auto-open="true"
  defer
></script>

Programmatic control

If you need to control the widget from your own JavaScript (e.g., open it when a button is clicked, change the agent dynamically), use the global namespace window.ctplai.cbp:

// Initialize manually (skip the data-agent-id auto-init)
window.ctplai.cbp.init({
  agentId: "agent_abc123",
  autoOpen: false,
  position: "bottom-right",
});
 
// Open the widget
window.ctplai.cbp.open();
 
// Close the widget
window.ctplai.cbp.close();
 
// Toggle open/close
window.ctplai.cbp.toggle();
 
// Send a message programmatically (e.g., greet a returning user)
window.ctplai.cbp.sendMessage("I'd like help with billing");
 
// Update config without re-mounting (e.g., switch language)
window.ctplai.cbp.updateConfig({ locale: "fr" });
 
// Tear down completely
window.ctplai.cbp.destroy();
 
// Check if initialized
if (window.ctplai.cbp.isInitialized()) {
  // ...
}

Skip the auto-init by omitting data-agent-id from the script tag, then call init() yourself when ready:

<script src="https://cdn.ctpl.ai/widget/chatbot-widget.js" defer></script>
<script>
  window.addEventListener("load", () => {
    document.getElementById("get-help-button").addEventListener("click", () => {
      window.ctplai.cbp.init({ agentId: "agent_abc123" });
      window.ctplai.cbp.open();
    });
  });
</script>

Theming and appearance

The widget's colors, branding, and copy are configured server-side on the agent itself — not in the embed snippet. Change them from Dashboard → Agents → your chat agent → Appearance:

  • Brand color — primary accent.
  • Logo / avatar — shown in the header.
  • Launcher icon — the floating button.
  • Welcome message — first thing visitors see when opening.

Updates take effect immediately on the next session (or call updateConfig() to refresh in-place).

Visitor identity

The widget identifies returning visitors using:

  • Fingerprint — via FingerprintJS, generates a stable browser identifier without cookies.
  • Session ID — stored in localStorage keyed per agent ID.

Both are sent to the chat WebSocket as query parameters so the agent can recognize returning visitors. No personal data is collected by the fingerprint — it's a hash of stable browser characteristics.

Page context

Each session sends the host page's context to the agent on connect:

  • pageUrl — the URL the widget was opened on.
  • pageReferrer — the previous URL.
  • userLocale — the visitor's browser locale.

Use these in your system prompt to make the agent context-aware (e.g., "if the visitor is on the pricing page, focus on plan comparison").

Style isolation

The widget renders inside a Shadow DOM. Your site's CSS will not affect the widget, and the widget's CSS will not affect your site. You don't need to worry about specificity wars or CSS leaks.

SSR frameworks (Next.js, Remix, etc.)

The auto-init code waits for the browser's load event before mounting, which avoids the React hydration mismatch warnings you'd otherwise get on SSR-hydrating frameworks. No special setup needed for Next.js, Remix, or similar.

If you use a Content Security Policy, add the CDN domain:

script-src 'self' https://cdn.ctpl.ai;
connect-src 'self' wss://app.ctpl.ai https://app.ctpl.ai;

Testing locally

The widget connects to wss://app.ctpl.ai/ws/chat/{agentId} by default. For local development against a self-hosted nerva-voice instance, override the WebSocket URL on the agent's Embed → Advanced tab.

Troubleshooting

SymptomLikely cause
Widget doesn't appearScript blocked by CSP, or data-agent-id missing. Check the browser console.
Widget appears but won't openagent_xxx is wrong or the agent is inactive. Verify in the dashboard.
Widget overlaps a page elementUse data-position to relocate, or hide it on specific pages with window.ctplai.cbp.destroy().
Conversation never connectsWebSocket connection failed. Check Network tab for the wss:// request. CSP connect-src may be blocking.
Hydration warnings on Next.jsMake sure the script is rendered inside <Script src="..." strategy="afterInteractive" /> or in a plain <script> tag with defer. Don't render inside a client component's render body.

See also

  • Chat agents — configure the agent that powers the widget
  • Webhooks — get events when conversations happen
  • Lead capture — capture leads via in-widget forms

On this page