Integration guide
Sandbox CSP setup
Add frame-ancestors https://app.useincito.com to your sandbox's Content-Security-Policy so the Incito magic-click iframe loads. Snippets for Next.js, Express, Django, Rails, Nginx, and Cloudflare.
Why this matters
Incito's hosted demo landing embeds your sandbox in an iframe so visitors get the full in-page tour with audio without leaving the launch page. That iframe needs your sandbox CSP to allow app.useincito.com as an embedder. This is the same B2B onboarding step Intercom Tours, Navattic, and Supademo require.
The one line you need
Add this directive to the Content-Security-Policy header on your sandbox route. Existing CSP entries stay; we're only adding frame-ancestors.
frame-ancestors 'self' https://app.useincito.com
Also remove X-Frame-Options. If your sandbox sets X-Frame-Options: SAMEORIGIN (Next.js default, Helmet default, Django SecurityMiddleware default), browsers honor that first and ignore your CSP frame-ancestors. Both must agree.
Pick your framework
App Router or Pages Router — same next.config.js config. The source path matches whichever route serves your sandbox demo.
module.exports = {
async headers() {
return [
{
source: '/demo/:path*',
headers: [
{
key: 'Content-Security-Policy',
value: "frame-ancestors 'self' https://app.useincito.com",
},
],
},
];
},
};
If you use a Next.js middleware that already sets a CSP header, merge the frame-ancestors directive into the existing policy rather than letting both compete.
Mount Helmet on the demo route only — applying it globally can break unrelated pages whose CSP is laxer by design.
import helmet from 'helmet';
app.use('/demo', helmet({
contentSecurityPolicy: {
useDefaults: true,
directives: {
'frame-ancestors': ["'self'", 'https://app.useincito.com'],
},
},
// X-Frame-Options is set by Helmet's default frameguard middleware
// and overrides CSP frame-ancestors in most browsers — disable it
// explicitly so the CSP directive is the only authority.
frameguard: false,
}));
Using django-csp (recommended) — install with pip install django-csp.
# Add csp.middleware.CSPMiddleware to MIDDLEWARE
CSP_FRAME_ANCESTORS = ("'self'", "https://app.useincito.com")
# If you use the legacy X_FRAME_OPTIONS setting, set it to None
# on the demo route — frame-ancestors supersedes it. Browsers
# honor X-Frame-Options first when both are set.
X_FRAME_OPTIONS = None # or remove SecurityMiddleware's X-Frame entirely
Per-view override is also fine if you only want to relax CSP on the demo route: @csp_update(FRAME_ANCESTORS=("'self'", "https://app.useincito.com")).
Rails 5.2+ ships a CSP DSL in config/initializers/content_security_policy.rb.
Rails.application.config.content_security_policy do |policy|
policy.frame_ancestors :self, "https://app.useincito.com"
end
# Disable X-Frame-Options on the demo route — CSP frame-ancestors
# is the modern authority and X-Frame-Options would override it.
# In your DemoController:
# class DemoController < ApplicationController
# after_action :strip_x_frame_options
# private
# def strip_x_frame_options
# response.headers.delete('X-Frame-Options')
# end
# end
For sandbox routes served behind Nginx as a reverse proxy or static origin. Apply on the specific location block, not server-wide.
location /demo/ {
# Allow Incito to embed this route in an iframe.
add_header Content-Security-Policy "frame-ancestors 'self' https://app.useincito.com" always;
# Remove X-Frame-Options if it's set upstream — browsers honor it
# before CSP frame-ancestors and the SAMEORIGIN default would
# block our iframe.
proxy_hide_header X-Frame-Options;
proxy_pass http://upstream;
}
The always flag emits the header on error responses (4xx/5xx) too, so a 502 from your upstream still tells the browser the iframe is allowed.
Cloudflare Transform Rules → Modify Response Header. Targets the response after your origin replies, so the rule wins even if your origin sets a stricter header.
# Rule 1: Set frame-ancestors on /demo/* responses
When incoming response matches:
(http.request.uri.path matches "/demo(/.*)?$")
Then:
Set static · Content-Security-Policy
Value: frame-ancestors 'self' https://app.useincito.com
# Rule 2: Remove X-Frame-Options on the same routes
When incoming response matches:
(http.request.uri.path matches "/demo(/.*)?$")
Then:
Remove · X-Frame-Options
Both rules ship as plain config in the Cloudflare dashboard under Rules → Transform Rules → Modify Response Header. No edge worker needed.
Verify the fix
Two-step check from your terminal. The first line confirms your CSP carries the directive; the second line confirms X-Frame-Options is gone (or absent).
# Replace with your sandbox URL.
SANDBOX_URL="https://app.yourdomain.com/demo"
curl -sI "$SANDBOX_URL" | grep -i 'content-security-policy'
# Expected: ...; frame-ancestors 'self' https://app.useincito.com; ...
curl -sI "$SANDBOX_URL" | grep -i 'x-frame-options'
# Expected: (no output — header absent)
Or open your demo's Health page in the Incito dashboard at app.useincito.com — the iframe-ready badge there reflects the same probe and updates within minutes of your CSP change going live.
Common gotchas
- CDN preprocessing. Cloudflare and Fastly can inject their own CSP. Disable their CSP injection on the demo route or merge our directive into theirs.
- Nonces. If your CSP uses script nonces (
'nonce-...'),frame-ancestorsdoesn't care — nonces apply only to script-src and style-src. - Cookie SameSite. If your sandbox auth uses
SameSite=Strict, cookies drop inside the iframe. UseLaxorNone; Secureon cookies the demo route reads. - Multiple CSP headers. If your stack emits CSP from two layers (origin plus reverse proxy), browsers combine them by intersection — a stricter directive in one layer overrides a permissive one in another. Audit both.
Still stuck
Email support@useincito.com with your sandbox URL. We'll probe it from our side and reply with the exact directive that's missing — usually under 24 hours.