HelpStackDocs

Integrations

Widget Embed

Embed the HelpStack web chat widget on any website with a single script tag.

Quick facts#

Loaderhttps://helpstack.eu/widget.js
AuthNone — widget endpoints are public
TransportSocket.IO (primary) + REST fallback
Channel typeWEBSITE_CHAT
Rate limit~30 req/min/IP on config/FAQ/status; ~20 msg/min/visitor on the HTTP message fallback
Origin controlOptional per-channel domain allowlist (403 if not allowed)

The loader derives its baseUrl from its own <script src> origin, so the widget.js served from helpstack.eu automatically talks to HelpStack — there is nothing else to configure.

Embed snippet#

<script src="https://helpstack.eu/widget.js?id=CHANNEL_ID" async></script>

CHANNEL_ID is the id of your WEBSITE_CHAT channel. The loader auto-initializes from the ?id= query parameter — no extra JavaScript is required.

Where to get CHANNEL_ID#

In the dashboard go to Settings → Channels, open (or create) a Website Chat channel, and copy its id using the copy button shown next to the channel. That id is what you pass as ?id=CHANNEL_ID.

How the loader works#

  1. The script reads its own <script src> to determine:
    • baseUrl — the script's origin (e.g. https://helpstack.eu)
    • channelId — the ?id= query parameter
  2. It injects a floating chat bubble and an <iframe> pointing at:
    {baseUrl}/widget/{channelId}?visitorId={visitorId}
    
  3. The iframe opens a Socket.IO connection for real-time messaging, with an HTTP fallback when WebSocket is unavailable.

Visitor identity#

The loader generates a persistent visitor id stored in localStorage:

KeyValue format
chat_widget_visitor_idvisitor_<uuid>

The same visitorId is reused across page loads and sessions on that browser, so a returning visitor reconnects to their existing conversation. Clearing localStorage (or a different browser/device) produces a new visitor and a new conversation thread.

Programmatic API#

The loader exposes a global window.ChatWidget object:

window.ChatWidget.init('CHANNEL_ID'); // initialize manually (if not using ?id=)
window.ChatWidget.open();             // open the chat panel
window.ChatWidget.close();            // close the chat panel
window.ChatWidget.toggle();           // toggle open/closed

A command-queue helper is also available for the deferred form used in the snippet comment:

window.chat('init', 'CHANNEL_ID');

There is no client-tool registration API. The loader does not expose any registerTool() or client-side tool handler. Client-side agent tools are mediated by the server over the iframe socket (see Custom Agent Tools), not registered from your page's JavaScript.

Appearance configuration#

Appearance is configured in the dashboard, not via the embed snippet. The widget fetches it from the server (GET /api/widget/[channelId]/config) and applies it via CSS variables. Verified configurable fields:

FieldNotes
primaryColorHex color. Default #3b82f6
positionbottom-right | bottom-left | top-right | top-left
mobilePositionSame enum as position, applied on mobile viewports
bubbleIconTypecustom or a preset
bubbleIconNameOne of: message, help, smile, headphone, zap, heart
bubbleOffsetXHorizontal offset of the bubble
bubbleOffsetYVertical offset of the bubble
greetingMessageGreeting text shown when the panel opens
avatarUrlAgent/brand avatar
showOnlineStatusWhether to display online/offline status
offlineMessageMessage shown when the channel is offline

Public widget endpoints#

All endpoints are public (no auth) and IP/visitor rate-limited.

MethodPathPurpose
GET/api/widget/[channelId]/configWidget config (colors, position, greeting, avatar, status). ~30 req/min/IP
GET/api/widget/[channelId]/faqsUp to 5 FAQ chips for the greeting. ~30 req/min/IP
GET/api/widget/[channelId]/statusWhether the channel is currently online
POST/api/widget/[channelId]/uploadmultipart/form-data file upload
POST/api/widget/messageHTTP fallback to send a message when WebSocket is unavailable. ~20 msg/min/visitor

Upload response (POST /api/widget/[channelId]/upload):

{
  "storagePath": "...",
  "publicUrl": "...",
  "fileName": "...",
  "fileSize": 12345,
  "contentType": "image/png",
  "width": 800,
  "height": 600
}

width and height are present for images only.

Message fallback (POST /api/widget/message) — body:

{
  "channelId": "CHANNEL_ID",
  "visitorId": "visitor_<uuid>",
  "content": "Hello, I need help",
  "visitorInfo": {
    "userAgent": "...",
    "currentUrl": "https://example.com/pricing",
    "referrer": "https://google.com"
  }
}

visitorInfo is optional. The channel must be of type WEBSITE_CHAT or the request is rejected. On success the response envelope's data contains { messageId, conversationId, createdAt }.

Real-time events (Socket.IO)#

The iframe communicates with the server over Socket.IO.

Widget → server

EventPayload
joinchannelId, visitorId, visitorInfo { userAgent, currentUrl, referrer }
messageOutbound visitor message
typingVisitor typing indicator
heartbeatPresence keep-alive
tool:resultResult of a client-side tool execution
tool:errorError from a client-side tool execution

Server → widget

EventPayload
joinedInitial messages + widget config
messageNew agent message
agent_typingAgent typing indicator
tool:executeRequest to run a client-side tool in the browser

Domain allowlist#

A channel can restrict which origins are allowed to load the widget. When an allowlist is configured and the requesting origin is not on it, the server responds with 403. Configure the allowlist in the channel settings in the dashboard. Leave it empty to allow any origin.

Minimal example page#

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>HelpStack Widget Demo</title>
  </head>
  <body>
    <h1>My website</h1>
    <button onclick="window.ChatWidget.open()">Need help?</button>

    <!-- HelpStack widget -->
    <script
      src="https://helpstack.eu/widget.js?id=CHANNEL_ID"
      async
    ></script>
  </body>
</html>