Website Chat Widget
Add a secure AI support chat to your website using a public widget ID without exposing Agents API credentials.
The Website Chat Widget adds a customer-facing AI chat bubble to a website. Visitors can message a selected CloudStation agent from your site without seeing Agents API keys, client IDs, Project IDs, owner IDs, or private agent template IDs.
Use the widget for browser-based chat. Use the Agents API for trusted backend integrations.
The widget is browser-safe because the page only receives a public widget ID and public display configuration. CloudStation keeps routing, Agents API credentials, selected agent template IDs, and signing secrets on the backend.
Agents API vs Website Chat Widget
| Need | Use Agents API | Use Website Chat Widget |
|---|---|---|
| n8n, Zapier, Make | Yes | No |
| Backend application | Yes | No |
| Telegram or WhatsApp bot | Yes | No |
| Public website chat bubble | No | Yes |
| Browser JavaScript | No | Yes |
Requires x-api-key / x-client-id | Yes | No |
Uses public widget_id | No | Yes |
| Can be copied into static HTML | No | Yes |
How It Works
Website loads the script
Your site loads CloudStation's hosted widget script, usually:
<script src="https://app.cloud-station.io/widgets/chat.js"></script>Script reads public attributes
The script reads data-widget-id, visual colors, position, and optional labels from the script tag.
Widget fetches public config
The browser calls:
GET /v1/widgets/{widget_id}/configThis returns safe display data such as display name, avatar URL, welcome message, suggested prompts, and whether login is required.
Visitor starts a conversation
The widget calls the backend to create a widget conversation. CloudStation returns a signed widget conversation token that is safe to store in the visitor's browser.
Messages route privately
The browser sends messages using the widget token. CloudStation routes them to the configured Project and selected agent privately.
Assistant replies appear in the widget
The widget polls the conversation and shows its messages in the order CloudStation stored them, so each reply sits under the question it answers. It renders safe markdown in the chat panel.
Slow answers are not dropped. After a minute the widget shows "Still working on it…" and keeps waiting for up to five minutes, or for as long as a teammate has taken over the conversation. After that it shows "No reply yet. It will appear here when it arrives." and the reply appears as soon as it does, while the chat panel is open. Visitors can keep sending messages while a reply is pending.
What the Browser Can See
The browser can see:
- public widget ID
- display name
- avatar URL
- welcome message
- suggested prompts
- visual styling attributes
- widget conversation token for that visitor/conversation
The browser cannot see:
- Agents API key
- client ID
- Project ID
- owner user ID
- selected agent template ID
- backend routing rules
AGENT_WIDGET_TOKEN_SECRET- private Project settings
Setup in Project Settings
Open Project Settings
Navigate to your Project and open the settings drawer.
Expand Website Chat Widget
Open the Website Chat Widget section.
Pick the support agent
Choose which CloudStation agent should answer visitor messages.
Customize the chat experience
Set the visitor-facing display name, avatar override, first message (the welcome message), and suggested prompts.
Configure allowed websites
Add each origin where the widget is allowed to run, such as https://example.com and https://www.example.com.
Copy the install code
Paste the script snippet into your website, usually before </body> or in your site builder's custom code area.
Install Snippet
Production example:
<script
src="https://app.cloud-station.io/widgets/chat.js"
data-widget-id="customer-support"
data-api-base-url="https://charlie-back.cloud-station.io"
data-primary-color="#f97316"
data-accent-color="#fed7aa"
data-surface-color="#fffaf3"
data-position="right"
data-launcher-label="Ask support">
</script>Minimal example:
<script
src="https://app.cloud-station.io/widgets/chat.js"
data-widget-id="customer-support"
data-api-base-url="https://charlie-back.cloud-station.io">
</script>Local development example:
<script
src="http://localhost:5174/widgets/chat.js"
data-widget-id="cloudstation-demo-public"
data-api-base-url="http://localhost:8082"
data-launcher-label="Ask support">
</script>The script can be placed in <head> or before </body>. If it is placed in <head>, the widget mounts on the document root because <body> does not exist yet.
Script Attributes
| Attribute | Required | Description | Example |
|---|---|---|---|
data-widget-id | Yes | Public widget ID configured in Project Settings. | customer-support |
data-api-base-url | No | CloudStation API base URL. Defaults to https://charlie-back.cloud-station.io when omitted. | https://charlie-back.cloud-station.io |
data-primary-color | No | Main action/launcher color. | #f97316 |
data-accent-color | No | Accent and user bubble color. | #fed7aa |
data-surface-color | No | Chat panel background. | #fffaf3 |
data-text-color | No | Main text color. | #20130b |
data-muted-color | No | Secondary text color. | #7b6150 |
data-position | No | Launcher side. | right or left |
data-theme | No | Theme hint. | light or dark |
data-launcher-label | No | Text shown beside the launcher icon. | Ask support |
data-branding | No | Whether to show footer attribution. | true or false |
data-brand-url | No | Footer brand link. | https://cloud-station.io |
data-conversation-idle-minutes | No | Minutes without a message, sent or received, after which the widget starts a new conversation. Defaults to 60. | 30 |
The default footer attribution is:
Built on CloudStation by CharlieThis is product attribution. It does not mean the selected support agent is named Charlie.
Widget Identity: Name and Avatar
The visible agent identity comes from backend widget config.
Display name priority:
- Widget custom display name
- Selected agent template name
Customer Support
Avatar priority:
- Widget custom avatar URL
- Selected agent template image
- Widget fallback icon
The browser receives only the safe final display name and avatar URL. It does not receive the selected agent_template_id.
Welcome Message and Suggested Prompts
Use the welcome message to explain what visitors can ask.
Good examples:
Hi! I can help with CloudStation setup, billing, integrations, and troubleshooting.Tell me what you are trying to build and I will point you to the right next step.Suggested prompts should be short and specific:
How do I deploy an app?
Connect WhatsApp to my project
Where do I find API keys?
Explain pricingAllowed Websites
Allowed websites protect your widget from being embedded on unauthorized domains. Add origins, not full page URLs.
Good:
https://example.com
https://www.example.com
https://docs.example.com
http://localhost:8899
http://127.0.0.1:8899Bad:
example.com
https://example.com/pricing
*.example.com
file://...
nullRules:
- Include protocol:
https://orhttp://. - Include port for local development.
- Do not include page paths.
- Add both apex and
wwwdomains if both serve your site. - Do not allow
nullorigins. - Do not use
file://for testing.
Public vs Login-Required Widgets
Widgets can be public or login-required.
| Mode | Setting | Best for |
|---|---|---|
| Public website chat | Require login = false | Marketing sites, docs sites, public support pages. |
| Authenticated in-app support | Require login = true | Product dashboards, internal apps, logged-in customer portals. |
If Require login is enabled and a visitor is not authenticated, the backend returns Authentication required and the widget shows an error instead of creating a public conversation. The hosted chat.js script never sends a login token, so login-required widgets only work from a client that sends the visitor's CloudStation bearer token, such as the CloudStation app itself.
Rate Limits
Project Settings exposes widget limits:
| Limit | Meaning |
|---|---|
| Messages per user/visitor per day | Per authenticated user when login is required; per anonymous visitor when public. |
| Messages per minute | Burst protection for a visitor/user. |
| Total widget messages per day | Project-wide daily cap for this widget. |
| Max message length | Maximum length of a single visitor message. |
Rate limits are enforced by the backend, not by the browser.
Markdown Rendering
Assistant messages support safe lightweight markdown:
**bold**__bold__*italic*- inline code using backticks
[links](https://example.com)- bullet lists
- numbered lists
- paragraphs
The widget escapes HTML before applying markdown formatting. It does not render arbitrary HTML from assistant messages.
Conversation Storage and Token Recovery
The widget stores visitor conversation state in localStorage so the thread continues while the visitor moves around your site.
Stored data includes:
- widget ID
- conversation ID
- signed widget conversation token
- the time of the last message sent or received
What visitors get:
- History across pages. When a page loads, the widget restores the conversation and shows its messages under the welcome message.
- Panel state. If the chat panel was open, it stays open on the next page. This is kept per browser tab in
sessionStorage. - Several tabs, one thread. Every tab of your site shows the same conversation. A message sent in one tab appears in the others, and starting or clearing a conversation in one tab does the same in the others.
- Idle expiry. After 60 minutes without a message, the widget starts a new conversation. Change this with
data-conversation-idle-minutes. The backend token still expires after 30 days, whatever this setting. - New chat. The New chat button in the chat header asks for confirmation, then clears the conversation and shows the welcome message again. The next message starts a new conversation.
<script
src="https://app.cloud-station.io/widgets/chat.js"
data-widget-id="customer-support"
data-conversation-idle-minutes="30">
</script>The widget automatically recovers from stale conversation tokens. If the backend returns a recoverable token or conversation error, the widget clears the stale local conversation, creates a new conversation, and retries once.
Recoverable cases include:
- invalid conversation token
- expired conversation token
- missing conversation token
- widget mismatch
- conversation mismatch
- user mismatch
- conversation not found
Local Testing
Do not test by double-clicking an HTML file. A file:// page has a browser origin of null, which CloudStation blocks for security.
Bad:
file:///Users/me/Downloads/test.htmlGood:
cd ~/Downloads
python3 -m http.server 8899Then open:
http://localhost:8899/test.htmlAdd the local origin to allowed websites:
http://localhost:8899
http://127.0.0.1:8899If someone opens the widget from file://, the widget shows a friendly error explaining that the page must be served through a local web server.
Security Model
The widget uses backend-managed sessions and signed widget tokens.
Important properties:
- The widget script is public and safe to load from a website.
widget_idis public; it is not a secret.- Project routing is private on the backend.
- The selected agent template is private on the backend.
- Agents API keys are never sent to the browser.
- CORS is restricted to configured allowed websites.
file://andnullorigins are rejected.- Rate limits are enforced server-side.
- Conversation tokens are signed by backend secret and scoped to the widget/conversation/user.
The Website Chat Widget is safe for browser use. The Agents API is not. Never replace the widget with raw Agents API calls from a public website.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Widget does not appear | Script URL failed to load or missing data-widget-id | Check browser console and script tag. |
Could not reach the chat API | Wrong API base URL, blocked origin, or backend unavailable | Confirm data-api-base-url and allowed websites. |
Friendly file:// error | Page opened directly from disk | Serve the page with python3 -m http.server. |
Authentication required | Widget has Require login = true | Disable the login requirement for public websites; chat.js sends no login token. |
403 Widget origin is not allowed in the network tab | Current site origin not in allowed websites | Add exact origin including protocol and port. |
| Messages do not send | Rate limit, max message length, or token issue | Check backend response and widget settings. |
Invalid conversation token | Stale browser conversation | Widget should recover automatically; clear localStorage if needed. |
| Wrong agent name/avatar | Widget overrides or selected template changed | Check Project Settings display name, avatar URL, and support agent. |
| Localhost works but production fails | Production domain missing from allowed websites | Add https://your-domain.com and https://www.your-domain.com. |
Manual localStorage Reset
Visitors reset a conversation with New chat in the chat header. If you need to clear everything a widget stored during testing, run this in the browser console:
for (const key of Object.keys(localStorage)) {
if (key.includes('cloudstation') || key.includes('widget')) {
localStorage.removeItem(key);
}
}Or clear site data from browser devtools.