Skip to content

Webchat: every message after the first is rejected with "This website is not authorized to load this chat widget" (embedded widget, v1.6.0) #1166

Description

@grupoarcos

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

  1. Open the site page in a normal browser (Chrome/Safari, desktop or mobile), click the launcher.
  2. Send "hi" → delivered, bot replies.
  3. 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 WebchatWrapperWebchatMessageInput. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions