Integrations
Widget Embed
Embed the HelpStack web chat widget on any website with a single script tag.
Quick facts#
| Loader | https://helpstack.eu/widget.js |
| Auth | None — widget endpoints are public |
| Transport | Socket.IO (primary) + REST fallback |
| Channel type | WEBSITE_CHAT |
| Rate limit | ~30 req/min/IP on config/FAQ/status; ~20 msg/min/visitor on the HTTP message fallback |
| Origin control | Optional 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#
- The script reads its own
<script src>to determine:baseUrl— the script's origin (e.g.https://helpstack.eu)channelId— the?id=query parameter
- It injects a floating chat bubble and an
<iframe>pointing at:{baseUrl}/widget/{channelId}?visitorId={visitorId} - 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:
| Key | Value format |
|---|---|
chat_widget_visitor_id | visitor_<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:
| Field | Notes |
|---|---|
primaryColor | Hex color. Default #3b82f6 |
position | bottom-right | bottom-left | top-right | top-left |
mobilePosition | Same enum as position, applied on mobile viewports |
bubbleIconType | custom or a preset |
bubbleIconName | One of: message, help, smile, headphone, zap, heart |
bubbleOffsetX | Horizontal offset of the bubble |
bubbleOffsetY | Vertical offset of the bubble |
greetingMessage | Greeting text shown when the panel opens |
avatarUrl | Agent/brand avatar |
showOnlineStatus | Whether to display online/offline status |
offlineMessage | Message shown when the channel is offline |
Public widget endpoints#
All endpoints are public (no auth) and IP/visitor rate-limited.
| Method | Path | Purpose |
|---|---|---|
GET | /api/widget/[channelId]/config | Widget config (colors, position, greeting, avatar, status). ~30 req/min/IP |
GET | /api/widget/[channelId]/faqs | Up to 5 FAQ chips for the greeting. ~30 req/min/IP |
GET | /api/widget/[channelId]/status | Whether the channel is currently online |
POST | /api/widget/[channelId]/upload | multipart/form-data file upload |
POST | /api/widget/message | HTTP 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
| Event | Payload |
|---|---|
join | channelId, visitorId, visitorInfo { userAgent, currentUrl, referrer } |
message | Outbound visitor message |
typing | Visitor typing indicator |
heartbeat | Presence keep-alive |
tool:result | Result of a client-side tool execution |
tool:error | Error from a client-side tool execution |
Server → widget
| Event | Payload |
|---|---|
joined | Initial messages + widget config |
message | New agent message |
agent_typing | Agent typing indicator |
tool:execute | Request 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>