Environment
Self-hosted, images ghcr.io/chatbotxio/chatbotx-*:latest built 2026-09-08 (v1.6.0). Widget embedded on a different origin (https://www.example.com/page/, widget served from https://chat.example.com) with the embed code generated by the Web Chat settings dialog. authorizedDomains = [example.com]. Same code path is present on main (2c59740) and v1.7.0 changelog does not mention it.
Steps to reproduce
- Open the site page in a normal browser (Chrome/Safari, desktop or mobile), click the launcher.
- Send "hi" → delivered, bot replies.
- Send a second message → nothing happens. Console: the server action returns
{"serverError":"This website is not authorized to load this chat widget."}. Every message from then on is rejected until the iframe is reloaded.
What the network shows (captured request by request)
GET /webchat?…parentOrigin=https://www.example.com Referer: https://www.example.com/ → page renders, token minted with originHost=example.com
POST /webchat (init action) body.parentOrigin = https://www.example.com/
GET /webchat?… (RSC: 1) Referer: https://chat.example.com/webchat?… ← client-side refresh right after init
POST /webchat (text "hi") body.parentOrigin = https://www.example.com/ → 200, message created
POST /webchat (text "second") body.parentOrigin = https://chat.example.com/webchat?… → serverError not authorized
Root cause
app/(no-sidebar)/webchat/page.tsx derives embeddingOrigin from the referer header and passes it as the parentOrigin prop to WebchatWrapper → WebchatMessageInput. The RSC refresh that happens after the guest session is initialised re-renders the page with referer = the iframe's own URL, so the prop becomes https://chat.example.com/webchat?…. WebchatMessageInput keeps the initial defaultValues for the first submit, but onSuccess calls reset(defaultValues) with the re-memoised values (new prop), so from the second message on the form sends the wrong parentOrigin. The access token in the guest store was minted at first render with originHost = example.com, so verifyWebchatAccessToken fails (payload.originHost !== getHostFromOrigin(parentOrigin)) and handleCreateWebchatMessage throws 403. Postback buttons are unaffected because sendPostback uses getClientEmbeddingOrigin() (query param first) instead of the server prop.
Expected
The origin presented by the widget should stay the embedding site's origin for the lifetime of the session (e.g. read parentOrigin from the query string / getClientEmbeddingOrigin() in the text form too, or freeze the initial prop), so text messages after the first are accepted.
Notes
Environment
Self-hosted, images
ghcr.io/chatbotxio/chatbotx-*:latestbuilt 2026-09-08 (v1.6.0). Widget embedded on a different origin (https://www.example.com/page/, widget served fromhttps://chat.example.com) with the embed code generated by the Web Chat settings dialog.authorizedDomains = [example.com]. Same code path is present onmain(2c59740) and v1.7.0 changelog does not mention it.Steps to reproduce
{"serverError":"This website is not authorized to load this chat widget."}. Every message from then on is rejected until the iframe is reloaded.What the network shows (captured request by request)
Root cause
app/(no-sidebar)/webchat/page.tsxderivesembeddingOriginfrom therefererheader and passes it as theparentOriginprop toWebchatWrapper→WebchatMessageInput. The RSC refresh that happens after the guest session is initialised re-renders the page withreferer= the iframe's own URL, so the prop becomeshttps://chat.example.com/webchat?….WebchatMessageInputkeeps the initialdefaultValuesfor the first submit, butonSuccesscallsreset(defaultValues)with the re-memoised values (new prop), so from the second message on the form sends the wrongparentOrigin. The access token in the guest store was minted at first render withoriginHost = example.com, soverifyWebchatAccessTokenfails (payload.originHost !== getHostFromOrigin(parentOrigin)) andhandleCreateWebchatMessagethrows 403. Postback buttons are unaffected becausesendPostbackusesgetClientEmbeddingOrigin()(query param first) instead of the server prop.Expected
The origin presented by the widget should stay the embedding site's origin for the lifetime of the session (e.g. read
parentOriginfrom the query string /getClientEmbeddingOrigin()in the text form too, or freeze the initial prop), so text messages after the first are accepted.Notes
authorizedDomainsempty the page correctly refuses the embedded widget (fix(webchat): fail closed when no authorized domains are configured #1026), so there is no configuration that makes an embedded widget work for more than one message.getClientEmbeddingOrigin()likesendPostback).