An embedded chat widget built from a loader script and an iframe needs its origin allowed in four Content Security Policy directives: script-src for the loader, connect-src for its settings request, frame-src for the chat panel and img-src for a custom icon. It also needs style-src to permit inline styles, because the widget positions its button with them. For SpideyChat, the origin is the one in your snippet's src, normally spideychat.com.
Most small business sites don't send a CSP at all, and if yours doesn't, none of this applies. If the widget isn't appearing for other reasons, start with the chat widget troubleshooting checklist. This guide is for developers and site owners who run a policy and want to add a chat widget without weakening it more than needed.
What the widget loads, request by request
The SpideyChat snippet is one script tag. When a page runs it, the browser makes these requests from your page:
- The loader,
widget.js, from the SpideyChat origin. - A settings request to the same origin, which returns the chatbot's colour, greeting, icon and whether this website is on the chatbot's license.
- The chat panel, an iframe served from the SpideyChat origin. The "Report an issue" form opens in a second iframe from the same origin.
- The chatbot's icon image, only if you uploaded one.
- A small stylesheet the loader adds to your page, plus inline styles on the launcher and panel.
Everything that happens inside the chat iframe, such as sending messages, loading fonts and playing sounds, is governed by the iframe's own page, not by your policy. Your CSP only needs to cover the five items above.
The directive map
| What | Directive | Falls back to |
|---|---|---|
widget.js loader |
script-src |
default-src |
| Settings request | connect-src |
default-src |
| Chat panel and report form | frame-src |
child-src, then default-src |
| Custom chatbot icon | img-src |
default-src |
| Injected stylesheet and inline positions | style-src (or style-src-elem and style-src-attr) |
default-src |
The fallbacks matter. MDN's directive pages describe them: if frame-src is absent, the browser uses child-src, and if that's absent too, default-src. A policy with a tight default-src and no frame-src blocks the chat panel even though nobody wrote a rule about frames.
An example policy
Here is a minimal policy that runs the widget alongside your own resources. Adjust the rest to your site:
Content-Security-Policy:
default-src 'self';
script-src 'self' spideychat.com;
connect-src 'self' spideychat.com;
frame-src spideychat.com;
img-src 'self' data: spideychat.com;
style-src 'self' 'unsafe-inline';
The host is written without a scheme here. MDN's Content-Security-Policy reference explains that when the scheme is omitted, the scheme of the page's own origin is used, so on an HTTPS site this means HTTPS only. Copy the host name exactly as it appears in your snippet: a policy that allows one host name while the snippet loads from another is blocked just as if nothing were allowed. The install guide shows the snippet for each platform.
The style-src question
This is the directive that trips people up, because the failure looks like a broken widget rather than a blocked one.
The loader adds a <style> element for its label, greeting bubble and animations, and it sets the launcher's and panel's position through their style text. MDN's style-src documentation is specific here: without 'unsafe-inline', inline <style> elements and style attributes are blocked, and so is setting an element's style.cssText from JavaScript, while assigning single style properties one at a time is not. The result is a launcher with no fixed position, so it can end up as a plain button at the very bottom of the page, and a help label with no styling.
The widget doesn't accept a nonce for its styles today, so the working option is 'unsafe-inline' in style-src, or in style-src-elem and style-src-attr if you split them. This allows inline CSS only. It doesn't let inline scripts run, which is what script-src protects.
Nonce-based policies and strict-dynamic
Many modern policies don't list hosts in script-src at all. They give each page a random nonce and add 'strict-dynamic'. MDN's script-src page explains the effect: with 'strict-dynamic', trust goes to scripts that carry the nonce or a matching hash, and to scripts they load, while allowlist entries such as host names and 'self' are ignored.
So adding spideychat.com to script-src does nothing under that kind of policy. Instead, add your page's nonce to the snippet itself: keep the script tag exactly as you copied it and add a nonce attribute holding the same value your header sends. The loader doesn't fetch further scripts, so the nonce on that one tag is all script-src needs.
The nonce has to be generated fresh for each response, as MDN notes, so it must be inserted by the same server code that writes the header. connect-src, frame-src, img-src and style-src still need the entries above.
Headers you don't need to touch
- frame-ancestors on your own site controls who may frame your pages. It has no effect on the chat panel. Whether the panel may be framed is decided by the panel's own response, and SpideyChat serves it with a policy that allows embedding on any site.
- X-Frame-Options on your site is the same story: it protects your pages, not ours.
- Referrer-Policy: even if your site sends no referrer, the widget tells the chat iframe your site's origin (never the full page address). The chatbot's license check needs to know which website it's on, so this is deliberate.
Rolling it out without breaking the chat
Change the policy in report-only mode first. MDN describes Content-Security-Policy-Report-Only as a way to monitor violations without enforcing the policy, so you can see what would be blocked while everything keeps working. Once the reports are clean, switch the same policy to the enforcing header.
When something is blocked, the browser console names the directive. Match the symptom:
| Symptom | Likely directive |
|---|---|
| No button at all, console mentions the script | script-src |
| Button shows default blue and the default greeting | connect-src |
| Button opens an empty white panel | frame-src |
| Your custom icon is missing | img-src |
| Button sits unstyled at the bottom of the page | style-src |
A blank panel can also mean the website isn't on the chatbot's license, since the panel runs the same check. The console shows a separate [SpideyChat] warning in that case, and the troubleshooting doc covers it.
The integrations page lists the platforms where the snippet is a straight paste, and our documentation has the install and troubleshooting steps if you'd like to check your setup against them.