Content Security Policy (CSP) and Velaro
What is CSP and why does it matter?
A Content Security Policy is an HTTP response header that tells the browser which scripts are allowed to run on your page. If your site uses CSP, you need to authorize the Velaro embed script or it will be blocked. This guide shows you exactly how to do that.
The complete, always-current list of Velaro hosts to allow (a short wildcard policy and an explicit per-host list, for both Velaro Chat and Velaro Messaging) is in Content Security Policy for Velaro Chat and Velaro Messaging. Use that article for the host list; use this one for nonces, hashes and troubleshooting.
Recommended fix: allow the Velaro wildcard, especially for enterprise and multi-region deployments
The easiest and preferred fix is to allow https://*.velaro.com (and wss://*.velaro.com for connections) instead of a fixed list of individual Velaro servers.
Velaro runs redundant infrastructure across multiple regions so the service stays available if one region has a problem. Traffic can move between regional hosts during failover, scaling and maintenance. A policy locked to a fixed set of hostnames works today, but if Velaro serves your widget from a different regional host, your visitors' browsers will block it and chat can become unavailable or degraded, even though Velaro itself is healthy. For organizations that require fault tolerance and redundancy, the wildcard keeps your security policy aligned with that design and prevents an access outage caused by the policy rather than the service.
The wildcard only covers subdomains of velaro.com, which Velaro controls. If your security standards require an explicit host list, use article 772 and keep your policy current with it.
The simplest approach: allow by domain
Add Velaro's CDN to your script-src directive:
Content-Security-Policy: script-src 'self' https://*.velaro.com https://eastprodcdn.azureedge.net;
This is the easiest option and works for all server platforms. Add Velaro's API domain to connect-src as well:
Content-Security-Policy:
script-src 'self' https://*.velaro.com https://eastprodcdn.azureedge.net;
connect-src 'self' https://*.velaro.com wss://*.velaro.com https://*.service.signalr.net wss://*.service.signalr.net;
img-src 'self' data: https://*.velaro.com https://eastprodcdn.azureedge.net https://galleryuseastprod.blob.core.windows.net https://velaromessagingprod.blob.core.windows.net;
Nonce-based CSP ('strict-dynamic')
A nonce is a random one-time token generated on your server for each page request. You add it to your CSP header and to the Velaro script tag. With 'strict-dynamic', the browser trusts scripts that are dynamically loaded by nonce-authorized scripts.
Important: The Velaro shim (shim.js) loads the chat widget (frame.js) inside an about:blank iframe. That iframe inherits your page's CSP. Because strict-dynamic propagation does not cross document boundaries, you need to both add a nonce to the shim script tag and include https://*.velaro.com https://eastprodcdn.azureedge.net in your script-src. Velaro's shim forwards the nonce automatically — this covers the iframe load. Adding the CDN domain is belt-and-suspenders to ensure compatibility across all browsers.
Also allow the Velaro hosts in connect-src, frame-src, img-src and media-src. A nonce only covers scripts. Without the connect-src entries the widget loads but chat messages do not arrive. See the first policy above, or article 772.
Step 1: Generate a nonce on your server
The nonce must be cryptographically random and different for every request.
ASP.NET Core:
var nonce = Convert.ToBase64String(RandomNumberGenerator.GetBytes(16));
HttpContext.Items["csp-nonce"] = nonce;
Response.Headers.Append("Content-Security-Policy",
$"script-src 'self' 'nonce-{nonce}' 'strict-dynamic' https://*.velaro.com https://eastprodcdn.azureedge.net;");
Node.js / Express:
const crypto = require('crypto');
app.use((req, res, next) => {
res.locals.nonce = crypto.randomBytes(16).toString('base64');
res.setHeader('Content-Security-Policy',
`script-src 'self' 'nonce-${res.locals.nonce}' 'strict-dynamic' https://*.velaro.com https://eastprodcdn.azureedge.net;`);
next();
});
Next.js (middleware):
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 'self' 'nonce-${nonce}' 'strict-dynamic' https://*.velaro.com https://eastprodcdn.azureedge.net;`);
response.headers.set('x-nonce', nonce);
return response;
}
PHP:
$nonce = base64_encode(random_bytes(16));
header("Content-Security-Policy: script-src 'self' 'nonce-$nonce' 'strict-dynamic' https://*.velaro.com https://eastprodcdn.azureedge.net;");
Python / Django:
import secrets, base64
nonce = base64.b64encode(secrets.token_bytes(16)).decode()
response['Content-Security-Policy'] = (
f"script-src 'self' 'nonce-{nonce}' 'strict-dynamic' https://*.velaro.com https://eastprodcdn.azureedge.net;"
)
Ruby on Rails:
nonce = SecureRandom.base64(16)
response.headers['Content-Security-Policy'] =
"script-src 'self' 'nonce-#{nonce}' 'strict-dynamic' https://*.velaro.com https://eastprodcdn.azureedge.net;"
Java / Spring:
String nonce = Base64.getEncoder().encodeToString(new SecureRandom().generateSeed(16));
response.setHeader("Content-Security-Policy",
"script-src 'self' 'nonce-" + nonce + "' 'strict-dynamic' https://*.velaro.com https://eastprodcdn.azureedge.net;");
Cloudflare Workers:
const nonce = btoa(crypto.getRandomValues(new Uint8Array(16)).toString());
response.headers.set('Content-Security-Policy',
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic' https://*.velaro.com https://eastprodcdn.azureedge.net;`);
Step 2: Add the nonce to the Velaro script tag
<script nonce="YOUR_NONCE_HERE" src="https://eastprodcdn.azureedge.net/shim.js"
data-deployment-id="YOUR_DEPLOYMENT_ID">
</script>
Velaro's shim reads the nonce from its own <script> tag and forwards it to the frame bundle it loads — you only need to set it once on the outer tag.
CSP hash alternative (static pages only)
If your page never changes, you can hash the script tag instead of using a nonce:
1. Take the exact content of your inline script block.
2. Generate a SHA-256 hash: echo -n "script content" | openssl dgst -sha256 -binary | base64
3. Add it to your header: script-src 'self' 'sha256-HASH_VALUE' https://*.velaro.com https://eastprodcdn.azureedge.net;
This only works for inline scripts. For external scripts loaded from a src=, use domain allowlisting or nonces.
Google Fonts with CSP
If you use Google Fonts alongside Velaro, add these to your CSP:
Content-Security-Policy:
script-src 'self' https://*.velaro.com https://eastprodcdn.azureedge.net;
style-src 'self' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://*.velaro.com wss://*.velaro.com https://*.service.signalr.net wss://*.service.signalr.net;
img-src 'self' data: https://*.velaro.com https://eastprodcdn.azureedge.net https://galleryuseastprod.blob.core.windows.net https://velaromessagingprod.blob.core.windows.net;
frame-src 'self' https://*.velaro.com https://eastprodcdn.azureedge.net;
media-src 'self' https://*.velaro.com https://galleryuseastprod.blob.core.windows.net https://velaromessagingprod.blob.core.windows.net;
Subresource Integrity (SRI)
SRI lets the browser verify that the Velaro script has not been tampered with in transit. Add the integrity and crossorigin attributes to the script tag:
<script
src="https://eastprodcdn.azureedge.net/shim.js"
integrity="sha256-HASH_FROM_VELARO"
crossorigin="anonymous">
</script>
Contact Velaro support for the current integrity hash when you need it.
Permissions Policy
Restrict browser features the Velaro widget does not need. Paste this header alongside your CSP:
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
If you use Velaro Video Chat, allow camera and microphone for your own origin:
Permissions-Policy: camera=(self), microphone=(self), geolocation=(), payment=()
Troubleshooting CSP issues
| Symptom | Likely cause | Fix |
|---|---|---|
| Widget does not load, console shows "Refused to execute script" | Missing script-src entry |
Add https://*.velaro.com https://eastprodcdn.azureedge.net or nonce to script-src |
| Widget loads but chat messages fail | Missing connect-src |
Add https://*.velaro.com wss://*.velaro.com https://*.service.signalr.net wss://*.service.signalr.net |
| Avatar or images broken | Missing img-src |
Add https://*.velaro.com https://eastprodcdn.azureedge.net to img-src |
| Nonce mismatch | 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 blocked on nonce-only CSP | strict-dynamic doesn't cross iframe boundary |
Add https://*.velaro.com https://eastprodcdn.azureedge.net to script-src alongside the nonce |
| Chat works from some regions or at some times but is blocked at others | Policy lists individual Velaro hosts and Velaro served the widget from a different regional host | Use the https://*.velaro.com and wss://*.velaro.com wildcard so failover between regions is never blocked |
You do NOT need 'unsafe-eval' or 'unsafe-inline' for Velaro.
Was this article helpful?