How the allowed domains list works
Each widget stores a list of allowed origins in itsdomains field. The behavior depends on whether that list is empty.
| Domains list | Behavior |
|---|---|
| Empty | The widget can be embedded on any website. Intended for development only. |
| Non-empty | The widget loads only on origins in the list. All other origins are blocked. |
https:// is prepended automatically. Only http and https are accepted.
https://example.com and https://www.example.com are different entries. Add each host you embed on, including subdomains and any http://localhost origin you test with.
Where the checks happen
The allowed domains list is enforced in two places: the embed loader script and the widget iframe.Embed loader
The loader script returned from/public/embed/{id} fetches the widget’s domains list. If the list is non-empty and window.location.origin is not in it, the loader logs an error and returns without creating the iframe — the widget never renders. If the list is empty, the loader proceeds.
The loader also derives parentOrigin from the real embedding origin (window.location.origin) and passes it to the iframe. Any parentOrigin value in the incoming query string is stripped first, so a page cannot spoof which origin it claims to be.
Widget iframe
Inside the iframe, the widget resolves atargetOrigin before it posts any message to the parent window:
| Condition | targetOrigin used for postMessage |
|---|---|
domains is empty | parentOrigin, or "*" if none |
parentOrigin is in domains | parentOrigin |
parentOrigin is not in domains | "null" — communication is blocked |
Public widget endpoints
The widget’s runtime endpoints live under the/core/public/web-widgets/{id} prefix and are unauthenticated — visitors call them directly from the browser with no Authorization header. Instead of a credential, the owning organization is resolved from the widget id: the service looks up the org from the widget id against a global lookup, then opens an org-scoped database client for all further work.
| Endpoint | Purpose |
|---|---|
GET /core/public/web-widgets/{id} | Return the widget config used to render the embed. |
GET /core/public/web-widgets/{id}/conversations | List a visitor’s conversations for this widget. |
POST /core/public/web-widgets/{id}/conversations | Start a new conversation or resume one by conversation_id. |
POST /core/public/web-widgets/{id}/conversations/{conversation_id}/feedback | Submit post-conversation feedback. |
- The org is fixed by the widget id, so a request can only ever touch data in that one organization.
- A conversation passed by
conversation_idis rejected with403if it does not belong to the widget. - Feedback payloads are size-capped, restricted to the widget’s configured feedback-form field ids, and accepted only once per conversation.
These public endpoints are separate from the authenticated management API. To create, update, or read widgets programmatically with a token, see the Web widgets API overview.
Visitor user ids
A visitor is not a logged-in user, so the widget generates aguest_ user id in the browser and stores it in localStorage. The storage key is scoped to both the widget id and the parent origin:
unknown.
Pre-launch checklist
Add your production origins
In the widget’s Allowed Domains settings, add every origin you embed on — your production host, any subdomains, and your local
http://localhost origin for testing.Confirm the list is non-empty
An empty list allows embedding anywhere. The dashboard shows a warning while the list is empty.
Related
Installing the snippet
Add the embed loader to your site.
Web widgets API
Manage widgets programmatically with the authenticated API.

