Content Security Policy (CSP) and Velaro
The Velaro live chat widget is fully compatible with Content Security Policy (CSP). No unsafe-eval or unsafe-inline is required for scripts.
The simplest approach: allow by domain
Add Velaro's CDN to your script-src directive:
`` Content-Security-Policy: script-src 'self' https://app-cdn.velaro.com; ``
This works for all server platforms and requires no per-request server logic.
Nonce-based CSP (most secure)
A nonce is a cryptographically random one-time token generated on your server for each page request. You add it to your CSP header and to the Velaro embed script tag — the browser only runs scripts with a matching nonce.
Because the Velaro embed script uses strict-dynamic, the nonce only needs to go on the one embed tag. All scripts the embed loads (shim.js, frame.js) are trusted automatically through the chain — no need to list CDN domains in script-src.
Step 1: Generate a nonce on your server
The nonce must be regenerated on every request. Never cache or hardcode it.
ASP.NET Core:
``csharp var nonce = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)); HttpContext.Items["csp-nonce"] = nonce; Response.Headers.Append("Content-Security-Policy", $"script-src 'nonce-{nonce}' 'strict-dynamic'; connect-src https://.velaro.com wss://.velaro.com https://app-cdn.velaro.com https://*.service.signalr.net;"); ``
Node.js / Express:
``javascript const crypto = require('crypto'); app.use((req, res, next) => { res.locals.nonce = crypto.randomBytes(32).toString('base64'); res.setHeader('Content-Security-Policy', script-src 'nonce-${res.locals.nonce}' 'strict-dynamic'; connect-src https://.velaro.com wss://.velaro.com https://app-cdn.velaro.com;); next(); }); ``
Next.js (middleware):
``javascript import { NextResponse } from 'next/server'; import crypto from 'crypto'; export function middleware(request) { const nonce = Buffer.from(crypto.randomUUID()).toString('base64'); const response = NextResponse.next(); response.headers.set('Content-Security-Policy', script-src 'nonce-${nonce}' 'strict-dynamic'; connect-src https://.velaro.com wss://.velaro.com https://app-cdn.velaro.com;); response.headers.set('x-nonce', nonce); return response; } ``
PHP:
``php $nonce = base64_encode(random_bytes(32)); header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic'; connect-src https://.velaro.com wss://.velaro.com https://app-cdn.velaro.com;"); ``
Python / Django:
``python import secrets, base64 nonce = base64.b64encode(secrets.token_bytes(32)).decode() response['Content-Security-Policy'] = f"script-src 'nonce-{nonce}' 'strict-dynamic'; connect-src https://.velaro.com wss://.velaro.com https://app-cdn.velaro.com;" ``
Ruby on Rails:
``ruby nonce = SecureRandom.base64(32) response.headers['Content-Security-Policy'] = "script-src 'nonce-#{nonce}' 'strict-dynamic'; connect-src https://.velaro.com wss://.velaro.com https://app-cdn.velaro.com;" ``
Java / Spring:
``java String nonce = Base64.getEncoder().encodeToString(new SecureRandom().generateSeed(32)); response.setHeader("Content-Security-Policy", "script-src 'nonce-" + nonce + "' 'strict-dynamic'; connect-src https://.velaro.com wss://.velaro.com https://app-cdn.velaro.com;"); ``
Cloudflare Workers:
``javascript const nonce = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32)))); response.headers.set('Content-Security-Policy', script-src 'nonce-${nonce}' 'strict-dynamic'; connect-src https://.velaro.com wss://.velaro.com https://app-cdn.velaro.com;); ``
Step 2: Add the nonce to the Velaro embed script tag
``html ``
Replace YOUR_NONCE_HERE with the nonce your server generates per request.
Full CSP directive reference
| Directive | Required values | Purpose |
|---|---|---|
script-src | 'nonce-<value>' 'strict-dynamic' | Authorizes the Velaro embed script and all scripts it loads dynamically |
connect-src | https://.velaro.com | API calls (visitor, engagement, main API) |
wss://.velaro.com | Real-time WebSocket connections (SignalR) | |
https://app-cdn.velaro.com | CDN asset loading | |
https://.service.signalr.net | Azure SignalR service connections | |
style-src | 'unsafe-inline' | Widget applies inline styles for positioning and theming |
https://fonts.googleapis.com | Google Fonts stylesheets (if widget fonts are configured) | |
font-src | https://fonts.gstatic.com | Google Font files |
img-src | data: | Agent headshots, company logos, inline SVG icons |
frame-src | 'self' blob: | Widget renders inside an iframe for isolation |
Optional directives by feature:
| Feature | Directive | Value |
|---|---|---|
| Chat notification sounds | media-src | https://app-cdn.velaro.com |
| Calendly scheduling | frame-src | https://calendly.com |
| Video Chat | Permissions-Policy | camera=(self), microphone=(self) |
CSP hash alternative (static pages only)
If your page content never changes, you can hash the inline script instead of using a nonce:
- Copy the exact content of the Velaro inline script block.
- Generate a SHA-256 hash:
echo -n "script content" | openssl dgst -sha256 -binary | base64 - Add to your header:
script-src 'self' 'sha256-HASH_VALUE' 'strict-dynamic';
This only works for inline scripts. For external scripts loaded via src=, use domain allowlisting or nonces.
Hosted platforms (WordPress, Webflow, Squarespace)
WordPress: Use a CSP plugin such as "CSP Nonce" or "HTTP Headers" that handles nonce generation automatically. Configure the plugin to apply nonces to inline scripts on all pages where Velaro is embedded.
Webflow / Squarespace / Wix: These platforms do not support server-side nonce generation. Use a Cloudflare Worker as a reverse proxy to inject CSP headers and nonces into responses. If CSP is not a hard requirement, the domain allowlist approach (script-src 'self' https://app-cdn.velaro.com) works without server-side changes.
Permissions Policy
Restrict browser features the Velaro widget does not use:
`` Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=() ``
If Velaro Video Chat is enabled, allow camera and microphone for the origin:
`` Permissions-Policy: camera=(self), microphone=(self), geolocation=(), payment=() ``
Monitoring CSP violations
Before enforcing a CSP in production, use Content-Security-Policy-Report-Only to collect violations without blocking anything:
`` Content-Security-Policy-Report-Only: script-src 'nonce-``
Switch to Content-Security-Policy once the report shows no violations.
Testing your configuration
- Open the page with the Velaro widget.
- Open browser DevTools (F12) → Console tab.
- Look for
velaro:initializingandvelaro:initialized— widget loaded successfully. - No red CSP errors in the console = policy is correct.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Widget does not load, console shows "Refused to execute script" | Missing script-src entry | Add https://app-cdn.velaro.com or nonce to script-src |
| "Refused to load script from...shim..." | Nonce missing or mismatched on embed tag | Verify the nonce= attribute is set on the embed script and matches the CSP header exactly |
| Widget loads but chat messages fail | Missing connect-src | Add https://.velaro.com wss://.velaro.com to connect-src |
| Avatar or images broken | Missing img-src | Add https://app-cdn.velaro.com to img-src |
| Nonce mismatch on every load | Nonce is cached or static | Nonce must be regenerated on every request — never cache it |
| Fonts broken | Missing font-src | Add https://fonts.gstatic.com to font-src |
| Widget frame blocked | Missing frame-src | Add blob: to frame-src |
| "Refused to evaluate a string as JavaScript" | Old version of Velaro widget | unsafe-eval is not needed — contact Velaro support to confirm you are on the current widget version |
Contact Velaro support with your full CSP header value and the browser console error messages if issues persist.
Was this article helpful?