Skip to main content
The allowed domains list controls which origins are permitted to embed a widget. The widget’s data endpoints are public and unauthenticated, so the org that owns the widget is resolved from the widget id rather than from a credential. Add your production domain(s) before launch so the widget refuses to load anywhere else.

How the allowed domains list works

Each widget stores a list of allowed origins in its domains field. The behavior depends on whether that list is empty.
Domains listBehavior
EmptyThe widget can be embedded on any website. Intended for development only.
Non-emptyThe widget loads only on origins in the list. All other origins are blocked.
When you add a domain in the dashboard, the input is normalized to an origin — protocol plus host, with no path or trailing slash. If you omit the protocol, https:// is prepended automatically. Only http and https are accepted.
yourdomain.com          ->  https://yourdomain.com
http://localhost:3000   ->  http://localhost:3000
https://app.example.com/checkout  ->  https://app.example.com
Because matching is exact on the normalized origin, 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.
Leaving the list empty allows the widget to load on any site. Add your production domain(s) before you publish.

Where the checks happen

The allowed domains list is enforced in two places: the embed loader script and the widget iframe.
Browser on https://yourdomain.com

        │  loads /public/embed/{id} loader script

┌─────────────────────────────────────────────┐
│ Embed loader                                │
│ if domains is non-empty and                 │
│ window.location.origin not in domains       │
│   -> log error, do not create the iframe    │
└─────────────────────────────────────────────┘
        │  origin allowed

┌─────────────────────────────────────────────┐
│ Widget iframe                               │
│ resolves targetOrigin from parentOrigin:    │
│   domains empty   -> parentOrigin or "*"     │
│   parentOrigin in domains -> parentOrigin    │
│   otherwise -> "null" (block postMessage)    │
└─────────────────────────────────────────────┘

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 a targetOrigin before it posts any message to the parent window:
ConditiontargetOrigin used for postMessage
domains is emptyparentOrigin, or "*" if none
parentOrigin is in domainsparentOrigin
parentOrigin is not in domains"null" — communication is blocked
When the parent origin is not in the allowed list, the iframe logs an error and refuses to post resize and viewport messages to the parent. The widget’s layout (resize, mobile full-screen) only works when the parent origin is allowed.

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.
GET    /core/public/web-widgets/{id}
GET    /core/public/web-widgets/{id}/conversations
POST   /core/public/web-widgets/{id}/conversations
POST   /core/public/web-widgets/{id}/conversations/{conversation_id}/feedback
EndpointPurpose
GET /core/public/web-widgets/{id}Return the widget config used to render the embed.
GET /core/public/web-widgets/{id}/conversationsList a visitor’s conversations for this widget.
POST /core/public/web-widgets/{id}/conversationsStart a new conversation or resume one by conversation_id.
POST /core/public/web-widgets/{id}/conversations/{conversation_id}/feedbackSubmit post-conversation feedback.
Because these endpoints are unauthenticated, they enforce their own boundaries:
  • 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_id is rejected with 403 if 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 a guest_ user id in the browser and stores it in localStorage. The storage key is scoped to both the widget id and the parent origin:
anyreach-uid-{widgetId}-{parentOrigin}
This means the same browser gets a distinct visitor id per (widget, origin) pair. A visitor who returns to the same site keeps their id (so their prior conversations are listed), but the same browser visiting a different embedding site, or a different widget, gets a separate id. When no parent origin is available, the key falls back to unknown.

Pre-launch checklist

1

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.
2

Confirm the list is non-empty

An empty list allows embedding anywhere. The dashboard shows a warning while the list is empty.
3

Verify on the real domain

Load the widget on an allowed origin and confirm it renders. Load it on a disallowed origin and confirm the loader logs an authorization error and the widget does not appear.

Installing the snippet

Add the embed loader to your site.

Web widgets API

Manage widgets programmatically with the authenticated API.