Brand colors
One line lets the launcher gradient and header background follow your brand. If you pass only primary, the accent gradient stop is derived automatically.
primaryColorprimaryColorAccentWeb Customer Service
TypeScript + Shadow DOM, about 12 KB gzipped, and zero runtime dependencies. CDN, npm, and standalone link entry all ship from the same build.
<script async src="https://cs.sochatlive.com/widget.js" data-app-key="sk_xxxxxxxxxxxxxxxxx" data-api-base="https://api.starim.io"></script>
pnpm add @starim-io/cs-widget
import StarIMCS from '@starim-io/cs-widget'
StarIMCS.init({
appKey: 'sk_xxxxxxxxxxxxxxxxx',
apiBase: 'https://api.starim.io',
})# 站外渠道:对方页面无需嵌 script https://cs.sochatlive.com/?k=sk_xxxxxxxxxxxxxxxxx&u=u_42&nick=%E8%AE%BF%E5%AE%A2%E6%98%B5%E7%A7%B0 # 生产建议 admin 代签 ?t=&s= → 见下方「链接跳转入口」
This page already loads https://cs.sochatlive.com/widget.js (data-mock="true"). Try the floating button in the lower-right corner. The panel below covers the imperative API and event subscriptions.
The floating button in the lower-right corner is the real widget. Use the Playgroundfor full appearance debugging.
Choose one path or combine them: CDN / npm embeds the floating launcher into your site, while standalone links distribute a full-screen visitor entry through off-site channels. After your open platform request is approved, you receive an appKey. The examples on this page can run with data-mock="true" first so you can validate the UI and imperative API before switching to production parameters.
<script async src="https://cs.sochatlive.com/widget.js" data-app-key="sk_xxxxxxxxxxxxxxxxx" data-api-base="https://api.starim.io" data-locale="zh-CN" data-theme="auto" data-position="br"></script>
<script async src="https://cs.sochatlive.com/widget.js?appKey=sk_xxxxxxxxxxxxxxxxx&apiBase=https%3A%2F%2Fapi.sochatlive.com&locale=en-US"></script>
<script async src="https://cs.sochatlive.com/widget.js" data-defer-init></script>
<script>
// queue-style: commands are buffered while the script loads asynchronously
window.StarIMCS = window.StarIMCS || function () {
(window.StarIMCS.q = window.StarIMCS.q || []).push(arguments)
}
StarIMCS('init', {
appKey: 'sk_xxxxxxxxxxxxxxxxx',
apiBase: 'https://api.sochatlive.com', // replace with your private API origin when self-hosting
externalUserId: 'u_42',
nickname: 'Alex Lee',
extra: { orderNo: 'O-2026-001' },
onReady: () => console.log('widget ready'),
onMessage: (m) => console.log('msg', m),
})
</script>Commands are queued while the script loads asynchronously and replay automatically once the widget is ready. Use this when your host page already has its own initialization flow.
Ideal for SMS, email, QR codes, and other off-site channels: visitors open the link and land directly in a full-screen support window. No script embed is required on the destination page, and there is no domain allowlist requirement. You can enable it alongside CDN or npm embeds.
# Smallest possible link: appKey only https://cs.sochatlive.com/?k=sk_xxxxxxxxxxxxxxxxx # Include visitor identity (when your business user is already signed in) https://cs.sochatlive.com/?k=sk_xxxxxxxxxxxxxxxxx&u=u_42&nick=Alex%20Lee
ESM and CJS bundles ship with full TypeScript typings from the same source and build as the CDN asset, with the same ~12 KB gzipped footprint. For SSR frameworks, load the widget from a client-only hook.
# pnpm pnpm add @starim-io/cs-widget # npm npm install @starim-io/cs-widget # yarn yarn add @starim-io/cs-widget
import StarIMCS from '@starim-io/cs-widget'
await StarIMCS.init({
appKey: 'sk_xxxxxxxxxxxxxxxxx',
apiBase: 'https://api.sochatlive.com', // Replace with your private API origin when self-hosting
externalUserId: currentUser?.id,
nickname: currentUser?.name,
onReady: () => console.log('ready'),
onMessage: (m) => console.log(m),
})
StarIMCS.open({ prefill: 'I want to ask about my order' })The widget uses a three-layer identity strategy: externalUserId (your stable business ID) > fingerprint (a low-entropy browser hash) > visitorId (a localStorage UUID fallback). Matching any layer treats the user as the same visitor, and the platform merges identity records by priority. Visitor identities stay isolated from your product account system and are only used for customer service context.
A stable and unique identifier from your own business system. This has the highest priority, preserves conversation history across sites and devices, and merges earlier anonymous sessions after login.
A low-entropy browser hash composed from UA, screen, timezone, canvas/WebGL, fonts, and similar signals. Even after clearing storage or switching to private mode, it still reconnects roughly 60-80% of visitors. The SDK uploads only the hash, never the raw fingerprint source data.
The widget auto-generates a v_<uuid> in localStorage. It is the most stable identifier within the same browser. After storage is cleared, the L2 fingerprint becomes the safety net.
nickname, avatar, email, phone, and extra are all optional. extra accepts arbitrary JSON so you can pass order numbers, plans, or other business context.
// Sync identity immediately after login; no shutdown + init required
StarIMCS.identify({
externalUserId: currentUser.id, // Stable unique ID from your own business system
nickname: currentUser.name,
avatar: currentUser.avatar,
email: currentUser.email,
extra: { orderNo: 'O-2026-001' },
})
// User sign out
StarIMCS.identify({ externalUserId: null })Merge priority: StarIMCS.init() > data-* > query string > remote site configuration. All three channels can be layered freely.
| Field | data-* attribute | Type / default | Description |
|---|---|---|---|
| appKeyRequired | data-app-key | string | The site integration key issued when the admin console creates the site. |
| apiBase | data-api-base | string · defaults to the API subdomain | The backend base URL. Third-party websites usually set the API subdomain explicitly, such as https://api.starim.io, while private deployments override it with their own endpoint. |
| wsBase | data-ws-base | string · auto-derived | The WebSocket base URL. It is derived from apiBase by default and only needs to be overridden when API and WS traffic use different domains or proxy chains. |
| externalUserId | data-external-user-id | string | A stable business-side user ID (layer 1 in the three-level identity strategy). |
| nickname | data-nickname | string | The display nickname shown to agents. |
| avatar | data-avatar | string · URL | The avatar image URL. |
| data-email | string | The visitor email address. | |
| phone | data-phone | string | The visitor phone number. |
| extra | data-extra | JSON string | Business context such as order numbers or plans, visible to agents. |
| locale | data-locale | "zh-CN" | "en-US" | Force a locale. When omitted, the widget falls back to localStorage and browser detection. |
| theme | data-theme | "light" | "dark" | "auto" | Defaults to light. auto follows prefers-color-scheme. |
| position | data-position | "br" | "bl" | "tr" | "tl" | Floating launcher position. The default is bottom-right (br). |
| defaultOpen | data-default-open | boolean · default false | Open the panel automatically after the page finishes loading. |
| zIndex | data-z-index | number · default 2147483000 | The Shadow DOM container z-index. Adjust it only when the host page already uses an even higher overlay layer. |
| mock | data-mock | boolean · default false | Local mock mode. It skips the backend and is useful for demos or offline development. |
| — | data-defer-init | attribute | Disable auto-init. Must be paired with StarIMCS.init(). |
All methods are exposed on global window.StarIMCS . The table below maps one-to-one to the button groups in the Live Demo .
| Method | Params | Description |
|---|---|---|
| init(cfg) | InitConfig | Initialize manually (only when data-defer-init is enabled). |
| identify(profile) | { externalUserId?, nickname?, ... } | Sync login state without reconnecting or losing unread messages. |
| open(opts?) | { prefill?: string } | Open the panel and optionally prefill the input box. |
| close() | — | Close the panel. |
| toggle() | — | Toggle between open and closed. |
| sendMessage(text) | string | Append and send a visitor message directly from the panel. |
| setLocale(locale) | "zh-CN" | "en-US" | Switch the locale and persist it to localStorage. |
| on(event, handler) | WidgetEvent, fn | Subscribe to widget events. |
| off(event, handler) | WidgetEvent, fn | Unsubscribe from widget events. |
| shutdown() | — | Destroy the instance and release resources, usually on SPA sign-out. |
| version | — | Read-only field that exposes the current widget version. |
StarIMCS.open() // Open the panel
StarIMCS.close() // Close the panel
StarIMCS.toggle() // Toggle the panel
StarIMCS.open({ prefill: 'I need help with an order' }) // Open with prefilled text
StarIMCS.sendMessage('Hello') // Send a message directly
StarIMCS.identify({ externalUserId: 'u_42' }) // Sync the signed-in identity
StarIMCS.setLocale('en-US') // Switch locale and persist it
StarIMCS.shutdown() // Destroy the instance on SPA sign-outStarIMCS.on('ready', () => console.log('widget is ready'))
StarIMCS.on('open', () => console.log('panel opened'))
StarIMCS.on('close', () => console.log('panel closed'))
StarIMCS.on('message', (m) => {
// m: { id, conversationId?, from: 'visitor' | 'agent' | 'system',
// text, agentName?, createdAt }
console.log(`new ${m.from} message: ${m.text}`)
})
StarIMCS.on('error', (e) => console.warn(e.message))Event list: ready · open · close · message · error · identify · shutdown.
The widget currently ships with zh-CN and en-US . It shares the same SoChat client locale key localStorage['starim_locale'] , so signed-in visitors automatically inherit the host page locale when they open the chat panel.
// Wire it to your own site language switcher:
StarIMCS.setLocale('en-US')
// → persists to localStorage['starim_locale'] and survives refreshes
// Or update the storage key directly without the widget helper:
localStorage.setItem('starim_locale', 'en-US')Full appearance customization is available from v0.2 onward. Pass the fields through init()、data-* , or the query string. For a live preview, open /cs-demo in the Playground.
One line lets the launcher gradient and header background follow your brand. If you pass only primary, the accent gradient stop is derived automatically.
primaryColorprimaryColorAccentUse sizes from 40-96 px, choose circle or pill mode, add a "Chat with us" label, and supply either inline SVG or an image URL for the icon.
buttonSizebuttonShapebuttonLabelbuttonIconThe default distance from the screen edge is 24 px and can be overridden independently on the X and Y axes to avoid fixed footers or other floating UI.
offsetXoffsetYControl panel width (280-720), height (360-900), and radius (0-32). Mobile always falls back to 100vw / 100vh sizing.
panelWidthpanelHeightpanelRadiusOverride the default site name plus the "Online · replies within 30 seconds" subtitle, and swap in a solid color, gradient, or image independently from primaryColor.
headerTitleheaderSubtitleheaderBackgroundProvide a single URL and the widget renders it with cover sizing and an overlay on the panel ::before layer so message readability stays intact.
backgroundImage| Field | data-* attribute | Type / default | Description |
|---|---|---|---|
| primaryColor | data-primary-color | string · color | Brand primary color (#RGB / #RRGGBB / hsl(...) / rgb(...) / named colors all work). It drives the launcher gradient start and header background. Default: #2f54eb. |
| primaryColorAccent | data-primary-color-accent | string · color | Gradient end color. By default it is derived from primaryColor with a +12% brightness shift (for #RGB / #RRGGBB only). Pass it explicitly for precise control. |
| offsetX | data-offset-x | number · default 24 | Horizontal distance from the screen edge in px. Valid range: 0-200, with automatic clamping. |
| offsetY | data-offset-y | number · default 24 | Vertical distance from the screen edge in px. Valid range: 0-200. |
| panelWidth | data-panel-width | number · default 380 | Desktop chat panel width in px. Valid range: 280-720. Mobile width falls back to calc(100vw - 32px). |
| panelHeight | data-panel-height | number · default 580 | Desktop chat panel height in px. Valid range: 360-900. Mobile height falls back to calc(100vh - 48px). |
| panelRadius | data-panel-radius | number · default 16 | Outer panel radius in px. Valid range: 0-32. |
| buttonSize | data-button-size | number · default 56 | Launcher size in px. Valid range: 40-96. This is the diameter for circles and the height for pills. |
| buttonShape | data-button-shape | "circle" | "pill" · default circle | Launcher shape. pill mode can render a text label through buttonLabel. |
| buttonLabel | data-button-label | string | Text label shown only when buttonShape="pill". For example: "Chat with us". |
| buttonIcon | data-button-icon | string · SVG string or image URL | Custom launcher icon. Values starting with <svg are treated as inline SVG; everything else is handled as an image URL. The default is the built-in chat bubble SVG. |
| headerTitle | data-header-title | string | Header title that overrides the site name or default localized copy. |
| headerSubtitle | data-header-subtitle | string | Header subtitle that overrides the default "Online / offline · replies within 30 seconds" style copy. |
| headerBackground | data-header-background | string · color / gradient / url | Header background. Pass any valid CSS background value. The default follows the primaryColor gradient. |
| backgroundImage | data-background-image | string · URL | Background image URL for the full chat panel, rendered with cover sizing and a translucent overlay. |
This setup renders a rose-colored gradient launcher with a "Chat with us" label in pill mode, a 420×640 desktop panel with a 20 px radius, and a header that shows "Customer Support" plus a business-hours subtitle. The CDN and npm paths produce identical results.
<script async src="https://cs.sochatlive.com/widget.js" data-app-key="sk_xxxxxxxxxxxxxxxxx" data-api-base="https://api.sochatlive.com" data-primary-color="#f43f5e" data-button-shape="pill" data-button-label="Chat with us" data-button-size="60" data-panel-width="420" data-panel-height="640" data-panel-radius="20" data-offset-x="32" data-offset-y="32" data-header-title="Customer Support" data-header-subtitle="Business hours 09:00 - 22:00 (GMT+8)"></script>
import StarIMCS from '@starim-io/cs-widget'
await StarIMCS.init({
appKey: 'sk_xxxxxxxxxxxxxxxxx',
apiBase: 'https://api.sochatlive.com',
// v0.2 appearance customization
primaryColor: '#f43f5e', // // Rose tone instead of the default blue gradient
buttonShape: 'pill', // // Pill launcher
buttonLabel: 'Chat with us', // // Launcher text label
buttonSize: 60,
panelWidth: 420,
panelHeight: 640,
panelRadius: 20,
offsetX: 32,
offsetY: 32,
headerTitle: 'Customer Support',
headerSubtitle: 'Business hours 09:00 - 22:00 (GMT+8)',
// backgroundImage: 'https://your-cdn.com/bg.jpg', // // Optional
// buttonIcon: '<svg viewBox="0 0 24 24">...</svg>', // // Optional
})--cs-primary and --cs-panel-w on the Shadow DOM root, layered on top of the default theme without leaking into host-page CSS.primaryColor is provided, primaryColorAccent is derived with a +12% brightness delta (for #RGB / #RRGGBB only) so the gradient remains balanced. Pass both values when you need precise control.buttonSize stays within 40-96 and panelWidth stays within 280-720, so out-of-range values do not break layout.panelWidth and panelHeight only affect desktop mode. Mobile always falls back to calc(100vw - 32px) / calc(100vh - 48px) to avoid overflow.buttonIcon is treated as inline SVG when it starts with <svg; otherwise it is wrapped as an image URL. Make sure the source stays under your control.backgroundImage is rendered with cover sizing and a 0.18 opacity overlay on the panel ::before layer so chat bubbles remain readable over light or dark artwork.This path is ideal for projects that already have a build pipeline, such as Vue, React, Next, Nuxt, Remix, or Astro. The SDK ships ESM + CJS bundles with full TypeScript typings and shares the same core implementation as the CDN asset. Named exports work better for tree-shaking, so unused APIs such as shutdown stay out of your bundle.
@starim-io/cs-widget npm v0.2.1
ESM + CJS bundles · bundled typings · IIFE auto-bootstrap build in the same dist output · zero runtime dependencies.
Latest version
v0.2.1
Fetched live from the npm registry
Size (gzipped)
~12 KB
37 KB unpacked
Runtime dependencies
0
Browser built-ins only
License
MIT
Commercial use allowed
# pnpm pnpm add @starim-io/cs-widget # npm npm install @starim-io/cs-widget # yarn yarn add @starim-io/cs-widget
import StarIMCS from '@starim-io/cs-widget'
await StarIMCS.init({
appKey: 'sk_xxxxxxxxxxxxxxxxx',
apiBase: 'https://api.sochatlive.com', // Replace with your private API origin when self-hosting
externalUserId: currentUser?.id,
nickname: currentUser?.name,
onReady: () => console.log('ready'),
onMessage: (m) => console.log(m),
})
StarIMCS.open({ prefill: 'I want to ask about my order' })import { init, identify, open, on, version } from '@starim-io/cs-widget'
await init({
appKey: 'sk_xxxxxxxxxxxxxxxxx',
apiBase: 'https://api.starim.io',
})
on('message', (m) => console.log(m))
console.log('widget version', version)const { init, identify, open, on, version } = require('@starim-io/cs-widget')
await init({
appKey: 'sk_xxxxxxxxxxxxxxxxx',
apiBase: 'https://api.sochatlive.com', // Replace with your private API origin when self-hosting
externalUserId: currentUser?.id,
nickname: currentUser?.name,
})
console.log('StarIM CS SDK version:', version)cs-widget accesses window / document / localStorage, so it must load on the client only; otherwise SSR will throw window is not defined. Here are the standard patterns for two common frameworks:
// inside app.vue or layouts/default.vue
// or wrap with <ClientOnly>
import { onMounted } from 'vue'
import { init } from '@starim-io/cs-widget'
onMounted(() => {
init({ appKey: 'sk_xxxxxxxxxxxxxxxxx' })
})// inside app/layout.tsx
'use client'
import { useEffect } from 'react'
import { init } from '@starim-io/cs-widget'
export default function RootLayout({ children }: { children: React.ReactNode }) {
useEffect(() => {
init({
appKey: 'sk_xxxxxxxxxxxxxxxxx',
apiBase: 'https://api.sochatlive.com', // Replace with your private API origin when self-hosting
})
}, [])
return (
<html lang="en">
<body>{children}</body>
</html>
)
}| Condition | Target | Description |
|---|---|---|
| import | ./dist/index.js | ESM entry used automatically by Vite, Webpack 5, Rollup, and other modern bundlers. |
| require | ./dist/index.cjs | CommonJS entry for Node 18+, Webpack 4, Jest, and similar environments. |
| types | ./dist/index.d.ts / .d.cts | Full TypeScript declarations, including InitConfig, ChatMessage, WidgetEvent, and more. |
| unpkg / jsdelivr | ./dist/widget.js | IIFE auto-bootstrap bundle for direct CDN <script> usage. |
| ./widget.js | ./dist/widget.js | Explicit subpath for bundlers that need to reference the IIFE asset directly. |
unpkg / jsdelivr fields point to dist/widget.js and can be used as CDN-equivalent mirrors: unpkg / jsDelivr。
<script>): best for landing pages, marketing sites, WordPress, and similar setups where one line of script gets you online fast without a build step.import): best for Vue, React, Next, Nuxt, and other apps with an existing bundler so you get typings and tree-shaking.Ideal for off-site channels such as SMS / email / IM / QR codes / support business cards / ticket follow-ups where you cannot inject a <script>. Visitors tap the link and land directly in a dedicated full-screen support window, and the destination domain does not need to be allowlisted. It coexists with embedded integration and can be enabled at the same time.
Entry mode
The landing page defaults to https://cs.<your-im-domain>/ (the legacy /c/?k=... path is still supported). There is no floating launcher, the ChatPanel fills the viewport, and iOS safe areas are handled automatically.
Authentication strategy
Requests carry X-CS-Entry: link, so the backend skips originAllowList and validates against the site-level linkEntry configuration instead. You can optionally enable HMAC-SHA256 signatures + TTL to prevent forged externalUserId values.
The standalone base URL is configured in admin
The sample links below use https://cs.sochatlive.com as the current brand customer-service landing origin (see brand.yaml → website.csUrl). The effective URL prefix always comes from the admin setting "System Settings → Customer Service → Standalone Link Base URL": the preview link in the admin "Site → Standalone Link" dialog, the url field returned by the server-side signing API, and the cs-widget standalone.html landing page all use that configuration automatically. For private deployments or multi-brand setups, each brand only needs to update its admin setting once.
# Smallest possible link: appKey only https://cs.sochatlive.com/?k=sk_xxxxxxxxxxxxxxxxx # Include visitor identity (when your business user is already signed in) https://cs.sochatlive.com/?k=sk_xxxxxxxxxxxxxxxxx&u=u_42&nick=Alex%20Lee
Use this for internal testing or tightly controlled channels. Production should enable signatures (admin → Site → Standalone link → Enforce signature validation).
https://cs.sochatlive.com/?k=sk_xxxxxxxxxxxxxxxxx&u=u_42&nick=Alex%20Lee&t=1716268800&s=8f0a...
t is the Unix timestamp in seconds and s is the HMAC-SHA256 signature in hex. Once the site TTL expires (300 seconds by default), the signature becomes invalid.
# Compute t / s with the same algorithm in your Node.js / Python / Go backend
t = floor(Date.now() / 1000) # Unix timestamp in seconds
payload = appKey + "\n" + t + "\n" + (externalUserId || "") + "\n" + (nickname || "")
s = HMAC_SHA256(signSecret, payload).digest("hex") # Put this value into ?s=...
# Node.js example
import crypto from 'node:crypto'
const t = Math.floor(Date.now() / 1000)
const payload = `${appKey}\n${t}\n${externalUserId || ''}\n${nickname || ''}`
const s = crypto.createHmac('sha256', signSecret).update(payload).digest('hex')signSecret is returned by admin in plaintext only once, so store it securely. If you lose it, reset it and all previously issued signed links become invalid immediately.
Do not want to implement HMAC in your own backend? The admin console can generate signed links directly from "Site → Standalone Link → Generate signed link", or you can call:
# Admin Gateway · requires admin.cs.site.link.manage
POST /api/v1/admin/cs/sites/{siteId}/link-entry/sign
Authorization: Bearer <admin-jwt>
Content-Type: application/json
{ "externalUserId": "u_42", "nickname": "Alex Lee" }
# Response
{ "appKey": "sk_xxxx", "t": 1716268800, "s": "8f0a...",
"ttlSec": 300, "expiresAt": "2026-05-21T07:53:20.000Z" }The response already contains t / s / expiresAt, so you can append them back to the URL directly.
| URL param | Short key | Required | Description |
|---|---|---|---|
| appKey | k | Yes | Site appKey issued from the admin console. Use either the long key or the short key. |
| externalUserId | u | No | Stable business-side user ID (identity layer L1). |
| nickname | nick | No | Visitor display nickname. |
| t | — | No | Unix timestamp in seconds. Required together with s when the site enforces signature validation. |
| s | — | No | HMAC-SHA256 signature in hex. Used together with t. |
| apiBase | — | No | Backend API base URL. Defaults to the landing page origin. |
| locale | — | No | zh-CN / en-US. Overrides the site default locale. |
| theme | — | No | light / dark / auto. |
| primaryColor | — | No | Brand primary color. Overrides site theme.primaryColor. |
Operational notes
feature.cs.link_entry.enabled (admin → System config). When disabled, every request carrying X-CS-Entry: link returns 503.CsSite.linkEntry.enabled (admin → Site details → Standalone link). This lets you disable a single site temporarily.landingBrand inside "Standalone Link" to override the embedded site theme with a custom title, logo, primary color, and welcome copy.admin.cs.site.link.manage to configure standalone links; otherwise the related button stays hidden.The widget does not use eval / new Function or inline style injection; all DOM and CSS are isolated within the Shadow DOM. If your site enforces CSP, the minimum required configuration is below:
Content-Security-Policy: script-src 'self' https://cs.sochatlive.com; connect-src 'self' https://api.sochatlive.com wss://api.sochatlive.com; img-src 'self' data: https://api.sochatlive.com https://cs.sochatlive.com;
Origin on this list, preventing unauthorized embedding. Please submit all domains you plan to use when applying for a site.95% of integrations do not need to worry about these endpoints; the widget already handles handshakes, JWT renewal, message pushing, and fallback mechanisms. If you are building a deep integration (e.g., reusing visitor identity during SSR), please refer to the table below.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/v1/cs/handshake | appKey + Origin | Issues a visitor JWT, valid for 30 minutes. |
| POST | /api/v1/cs/identify | visitor JWT | Updates visitor profile (nickname, email, extra, etc.). |
| POST | /api/v1/cs/conversations | visitor JWT | Visitor sends a message (automatically creates conversation on first message). |
| GET | /api/v1/cs/conversations/current | visitor JWT | Retrieves current active conversation. |
| GET | /api/v1/cs/conversations/:id/messages | visitor JWT | Message list (supports incremental fetch via since). |
| POST | /api/v1/cs/conversations/:id/leave | visitor JWT | Visitor leaves conversation. |
| WS | /ws (subprotocols: sochat-cs, jwt.<token>) | visitor JWT | Pushes conversation events / CS messages. |
All REST requests automatically include X-Locale: <current_locale> to help the server return localized error messages.
When the widget encounters issues, check the browser console first. This table covers 99% of common problems.
| Symptom | Possible Cause | Fix |
|---|---|---|
| Floating button does not appear | Missing data-app-key; or script blocked by ad-blocker | Check console for [StarIMCS] no appKey; try offline verification in mock mode. |
| Button appears but unclickable | Host page CSP blocks connect-src | Add the CS API domains (HTTPS and WSS) to the CSP whitelist. |
| Handshake 403 origin denied | Current domain is not in the site's allowed embed list | Contact Open Platform support to add your domain to the site's whitelist. |
| Handshake returns available=false | CS feature not yet enabled for this site | Contact Open Platform support to enable CS feature or adjust site status. |
| WebSocket failed | Browser / Proxy blocks wss | No action needed. Automatically falls back to 8s short polling, transparent to users. |
| Button disappears after route change | SPA framework unmounted the node | widget >= 0.1.1 has built-in MutationObserver auto-recovery; consider upgrading to v0.2+ for appearance customization. |
| Still old nickname after user switch | Did not call identify | Always call StarIMCS.identify({ externalUserId, nickname }) when login state changes. |
| Styles overridden by host page | Shadow DOM isolation failed (theoretically should not happen) | Submit an issue with a minimal reproducible page. |
First, test the frontend interaction using mock mode. Once the Open Platform creates your widget site and issues an AppKey, replace mock mode with the official appKey to start integration. The production Widget URL and the demo on this page share the same build artifact.