CSP and what the widget stores in the browser
For developers, in code: the Content-Security-Policy a page needs, what the widget stores on the visitor's device, and what it never does.
Content-Security-Policy
If your page sets a CSP, the widget needs these allowances. Replace the two hosts with the ones in your install snippet.
| Directive | Value | Why |
|---|---|---|
script-src |
https://<widget host> blob: |
the bundle; blob: for the microphone’s audio worklet |
connect-src |
https://<agent host> wss://<agent host> |
GET /widget/config, the /ws and /ws/stt sockets, /tts, the beacons |
media-src |
blob: data: |
spoken answers are audio clips fetched from /tts and played from one <audio> element |
img-src |
https://<your CDN> |
the host of the launcher image, uploaded or pasted |
Styles are inside the shadow root and need no style-src allowance beyond what your page already
permits for inline styles there; if your policy forbids inline styles entirely, test — the widget
writes its CSS into a <style> element in its own shadow tree.
What the widget stores on the visitor’s device
Everything is in localStorage, per origin, best-effort: a browser with storage disabled or full
simply does not persist, and nothing breaks.
| Key | What | How long |
|---|---|---|
sa:widget:<code> |
up to 10 conversations of up to 50 messages each, the active one, whether the widget was open, the remembered mode (text or voice) | each conversation 7 days after its own last activity; the session fields 45 minutes after the last write |
sa:widget:prefs:<code> |
the dragged position, that the widget was ever opened, when the greeting teaser was dismissed, which proactive findings were shown on which path, the chosen spoken language | no expiry |
No cookies. Nothing is written for attribution: a campaign the visitor arrived on is sent with the first message of a conversation and never stored on the device, so navigating before chatting loses it — the accepted price of not handing the owner a consent obligation.
What the widget never does
- It never reads the page unless the owner turned page actions on, and then it reads state
(which fields exist, whether they are filled) — the text in a field leaves the browser only under
the owner’s
page_read_valuesswitch, and passwords, hidden fields and anything underdata-sa-privatenever. Reading the screen. - It never opens the microphone or plays sound without a press.
- It never navigates to another origin on its own; a link elsewhere is shown, not followed.
- It never stores the user token you relay with
Shukai.setUser; the token is held in memory and sent with each message. User identity.
Logging out on a shared device
Shukai.clearUser() forgets the relayed token. Shukai.clearHistory() wipes every conversation on
the device and the remembered mode. On a shared browser, call both at logout — the second is the
one that stops the next person seeing the previous visitor’s chats.
JavaScript API.
Where the data goes
Conversations are stored on servers in the EU for as long as the site’s plan keeps them, then
deleted; no IP address is stored; the owner’s data-and-control page says the rest. For the
developer, the one fact that matters: the runtime the widget talks to is the data-agent-base
in the snippet, and nothing else.