Configure CSP so the chatbot works on your website
If the MuseRelay chatbot doesn't work smoothly on your website and you see "Refused to connect" messages in the browser console, it's almost certainly a CSP (Content Security Policy) issue. Here's what it is, how to detect it, and how to fix it in minutes.
🔍 How to tell if your website has this problem
- Open your website where the MuseRelay chatbot is embedded.
- Press F12 (opens browser DevTools).
- Go to the Console tab.
- Send a message from the chatbot.
- If you see messages like these, you have a CSP issue:
Refused to connect to 'wss://muserelay.com/ws' because it violates
the following Content Security Policy directive: connect-src 'self' ...
Refused to load the script 'https://muserelay.com/bot/123/script.js'
because it violates the following Content Security Policy directive: script-src 'self' ...
❓ What is CSP and why does it affect me?
Content Security Policy is an HTTP header your server sends to the browser saying: "you can only connect to these domains, only run scripts from these others". It's a good security measure — prevents attackers from injecting malicious code.
The problem is when your CSP is too restrictive and blocks legitimate resources like the MuseRelay chatbot. The solution isn't removing CSP, it's adding an exception for our domains.
✅ What you need to allow for MuseRelay
MuseRelay needs your CSP to allow access to these domains and resource types:
| CSP directive | Domains to add | What for |
|---|---|---|
script-src |
https://muserelay.com |
Load the widget JavaScript |
connect-src |
https://muserelay.com wss://muserelay.com |
Real-time WebSocket + HTTP fallback requests |
img-src |
https://muserelay.com data: |
Bot logo and avatars |
style-src |
https://muserelay.com 'unsafe-inline' |
Widget styles (needs inline for theming) |
⚠️ The critical one is wss://muserelay.com under connect-src — without it, the chatbot falls back to HTTP polling and you lose the real-time experience (token-by-token streaming, instant responses, etc.).
🛠️ How to add it based on your platform
Nginx
Edit the server block of your domain:
add_header Content-Security-Policy "default-src 'self'; \
script-src 'self' https://muserelay.com 'unsafe-inline'; \
connect-src 'self' https://muserelay.com wss://muserelay.com; \
img-src 'self' https://muserelay.com data:; \
style-src 'self' https://muserelay.com 'unsafe-inline';" always;
Then sudo nginx -t && sudo systemctl reload nginx.
Apache (.htaccess)
<IfModule mod_headers.c>
Header set Content-Security-Policy "default-src 'self'; \
script-src 'self' https://muserelay.com 'unsafe-inline'; \
connect-src 'self' https://muserelay.com wss://muserelay.com; \
img-src 'self' https://muserelay.com data:; \
style-src 'self' https://muserelay.com 'unsafe-inline';"
</IfModule>
Caddy
yoursite.com {
header Content-Security-Policy "default-src 'self'; \
script-src 'self' https://muserelay.com 'unsafe-inline'; \
connect-src 'self' https://muserelay.com wss://muserelay.com; \
img-src 'self' https://muserelay.com data:; \
style-src 'self' https://muserelay.com 'unsafe-inline';"
}
Then sudo systemctl reload caddy.
HTML meta tag (any CMS)
If you don't have server access, you can put it in the <head> of your HTML:
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' https://muserelay.com 'unsafe-inline';
connect-src 'self' https://muserelay.com wss://muserelay.com;
img-src 'self' https://muserelay.com data:;
style-src 'self' https://muserelay.com 'unsafe-inline';
">
frame-ancestors or report-uri. Works for 90% of cases.
Cloudflare (Transform Rules)
- Cloudflare panel → Rules → Transform Rules → Modify Response Header.
- Create a rule for your domain.
- Set dynamic → header
Content-Security-Policy→ paste the full value. - Deploy.
WordPress
Easiest: plugin HTTP Headers or Really Simple SSL Pro. They let you edit CSP from the admin.
Manual alternative: in your theme's functions.php (better yet, in a child theme):
add_action('send_headers', function() {
header("Content-Security-Policy: default-src 'self'; " .
"script-src 'self' https://muserelay.com 'unsafe-inline'; " .
"connect-src 'self' https://muserelay.com wss://muserelay.com; " .
"img-src 'self' https://muserelay.com data:; " .
"style-src 'self' https://muserelay.com 'unsafe-inline';");
});
🧪 How to check it works
- Reload your website with Ctrl+Shift+R (force cache refresh).
- Open the browser console (F12).
- Send a message from the chatbot.
- You should no longer see the "Refused to connect" error.
- In the Network tab look for
ws— you should see an active WebSocket connection (Status: 101 Switching Protocols).
If you see the WebSocket connection and the message appears word by word in the chat, you're done! Real-time streaming is working.
📝 If your hosting provider won't let you edit CSP
Some hosts (WordPress.com, Wix, basic Shopify, SaaS platforms like Musedock) won't let you modify HTTP headers. In that case you have two options:
- HTML meta tag (see above). Works almost always.
- Ask your provider to allow CSP overrides per domain. Copy and paste the text below into their support — it's written for devs and gives them all the technical info:
👉 Copy-paste prompt for your hosting provider
Hi. I have a CSP issue on the site I have hosted with you and I want to
propose an improvement so I can embed external tools (chatbots, third-party
widgets, analytics, etc.).
PROBLEM:
Your platform applies default Content-Security-Policy headers that prevent
legitimate external resources from working. For example, I'm embedding the
MuseRelay widget and the WebSocket gets blocked:
Refused to connect to 'wss://muserelay.com/ws' because it violates
the following Content Security Policy directive: connect-src 'self'
The widget falls back to HTTP polling (worse UX). The right thing is to be
able to authorize WebSocket to muserelay.com.
PROPOSAL:
Add fields to the site settings panel that allow CSP overrides per domain:
[1] Domains allowed for connect-src (textarea, one per line)
Example: wss://muserelay.com, https://api.stripe.com
[2] Domains allowed for script-src
Example: https://cdn.jsdelivr.net
[3] Domains allowed for frame-src / img-src
Behavior:
- Empty → default CSP (current).
- With values → appended to existing CSP (not replacing).
- Preview of final CSP before saving.
Use cases this solves:
- External chatbots/widgets (MuseRelay, Intercom, Tidio, Crisp).
- Analytics (Plausible, Fathom, self-hosted).
- Embedded forms (Typeform, Calendly).
- Videos (Wistia, self-hosted).
- Customer-owned APIs called from frontend JS.
Suggested implementation:
1. JSONB `csp_overrides` column in the sites table.
2. Fields in Settings controller.
3. vhost template (nginx/caddy) reads the arrays and appends to existing
CSP directives.
4. Preview endpoint for final resulting CSP.
5. Validation of allowed schemes (https://, wss://, ws:// only in dev).
Simple alternative: a single "Extra CSP headers" free-text textarea where
the user directly pastes directives. Less polished but works.
Can you add this to the roadmap? If there's already a way to customize CSP
and I haven't found it, please point me. Thanks.
🆘 If nothing works
While you fix the CSP, the chatbot still works — it just loses real-time. MuseRelay automatically detects if WebSocket is blocked and falls back to HTTP polling every 5 seconds. The user still gets the bot's replies, just with a small extra latency.
If you can't modify CSP and want to force polling mode so there are no console errors, write us at soporte@muserelay.com and we'll send you a script variant that starts directly in polling mode.
Useful resources
- Official CSP guide on MDN
- Google CSP Evaluator — paste your CSP and it tells you if there are issues.
- securityheaders.com — scans your domain and scores all security headers.