Skip to main content

Content Security Policy (CSP)

If your site sets a Content-Security-Policy HTTP header, you need to whitelist Datsnip. This page explains exactly which directives to add — and which approach to use depending on your CSP style.


Do I have a CSP?​

Open your browser DevTools → Network → click any page request → look for a Content-Security-Policy response header. If you see one, follow the guide below.

If you don't have one (most WordPress, Webflow, or Shopify sites don't), you can skip this page entirely.


What Datsnip needs​

Datsnip requires two CSP permissions:

DirectiveWhy
connect-srcThe SDK makes fetch and sendBeacon calls to your Datsnip endpoint to load rules and send analytics.
style-srcThe SDK injects <style> tags to hide elements and show the visual picker highlight.

Option 1 — unsafe-inline style (simplest)​

This is the right choice if your site uses a keyword-based CSP (e.g. style-src 'self' 'unsafe-inline').

Add your Datsnip endpoint to connect-src, and make sure style-src includes 'unsafe-inline':

Content-Security-Policy:
connect-src https://api.datsnip.com;
style-src 'self' 'unsafe-inline';

https://api.datsnip.com is the Datsnip endpoint: it serves the SDK file and receives events, and it is the ingestUrl in your snippet.


If your site uses a nonce-based CSP (style-src 'nonce-...') — common on SaaS products, finance, and enterprise sites — use this approach instead. It avoids adding 'unsafe-inline' entirely.

How it works​

Your server generates a fresh random nonce on every page request and puts it in the CSP header and on the script tags. Datsnip reads the nonce from its own <script> tag and automatically stamps it on every <style> tag it injects, so the browser accepts them.

Step 1 — Generate a nonce on your server​

The nonce must be a cryptographically random base64 string, regenerated for every request.

// Node.js example
const crypto = require('crypto')
const nonce = crypto.randomBytes(16).toString('base64')
# Python example
import secrets, base64
nonce = base64.b64encode(secrets.token_bytes(16)).decode()

Step 2 — Set the CSP header​

Content-Security-Policy:
connect-src https://api.datsnip.com;
style-src 'nonce-NONCE_VALUE';
script-src 'nonce-NONCE_VALUE';

Step 3 — Add the nonce to your snippet​

<!-- Anti-flicker style — include nonce here too -->
<style nonce="NONCE_VALUE" id="_gs_af">html{visibility:hidden;pointer-events:none}</style>
<noscript><style nonce="NONCE_VALUE">#_gs_af{visibility:visible!important}</style></noscript>
<script nonce="NONCE_VALUE">setTimeout(function(){var e=document.getElementById('_gs_af');if(e)e.remove()},3000)</script>

<!-- Datsnip SDK — set the config first; the nonce on this tag is what the SDK reads automatically -->
<script nonce="NONCE_VALUE">
window._sf = { siteId: 'YOUR_SITE_ID', ingestUrl: 'https://api.datsnip.com' }
</script>
<script nonce="NONCE_VALUE" src="https://api.datsnip.com/sdk/tracker.js" async></script>

That's it — no extra configuration. The SDK reads the nonce from its own <script> tag and uses it on every <style> tag it injects.

Why this works

The SDK captures document.currentScript.nonce synchronously when it first loads (before any async operations), then applies it to every <style> element it creates — including the hidden-elements stylesheet and the visual picker overlay.


Framework-specific examples​

Next.js App Router​

Next.js has built-in nonce support via its CSP middleware guide.

In middleware.ts:

import { NextResponse } from 'next/server'
import crypto from 'crypto'

export function middleware() {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
const csp = `
script-src 'nonce-${nonce}' 'strict-dynamic';
style-src 'nonce-${nonce}';
connect-src https://api.datsnip.com;
`.replace(/\s{2,}/g, ' ').trim()

const res = NextResponse.next()
res.headers.set('Content-Security-Policy', csp)
res.headers.set('x-nonce', nonce) // pass nonce to your layout
return res
}

In app/layout.tsx, read the nonce from the request header and put it on your Datsnip script tags:

import { headers } from 'next/headers'

export default function RootLayout({ children }) {
const nonce = headers().get('x-nonce') ?? ''
return (
<html>
<head>
<script nonce={nonce} dangerouslySetInnerHTML={{
__html: `window._sf = { siteId: 'YOUR_SITE_ID', ingestUrl: 'https://api.datsnip.com' }`
}} />
<script nonce={nonce} src="https://api.datsnip.com/sdk/tracker.js" async />
</head>
<body>{children}</body>
</html>
)
}

Express / Node.js​

app.use((req, res, next) => {
const nonce = require('crypto').randomBytes(16).toString('base64')
res.locals.nonce = nonce
res.setHeader('Content-Security-Policy', [
`script-src 'nonce-${nonce}'`,
`style-src 'nonce-${nonce}'`,
`connect-src https://api.datsnip.com`,
].join('; '))
next()
})

Then in your template:

<script nonce="<%= nonce %>">
window._sf = { siteId: 'YOUR_SITE_ID', ingestUrl: 'https://api.datsnip.com' }
</script>
<script nonce="<%= nonce %>" src="https://api.datsnip.com/sdk/tracker.js" async></script>

Troubleshooting​

Hidden elements aren't being hidden The most common cause is a style-src violation blocking the injected stylesheet. Open DevTools → Console and look for a CSP error mentioning style-src. Fix it using Option 1 or 2 above.

No analytics data arriving This is almost always a missing connect-src. Open DevTools → Network, look for failed requests to your Datsnip endpoint, then add it to connect-src.

The visual picker highlight doesn't appear Same as hidden elements — the _gs_picker_style tag is blocked by style-src. Apply Option 1 or 2.

I'm using a hash-based CSP ('sha256-...') Hash-based policies are not compatible with Datsnip because the SDK injects styles with dynamic content (selectors and colours change per-rule). Use the nonce approach instead.