Getting started
Setup walkthrough
Five steps to your first autonomous demo. Twelve minutes on Next.js, fifteen for everything else. The whole flow is also live in your dashboard at app.useincito.com — this guide mirrors it so your team knows what's coming before you sign up.
Before you start
Three things you'll have ready in the next twelve minutes:
- A public URL for your product (the domain prospects see when they sign up).
- An API key from your Incito dashboard — generated once at sign-up, kept in your secret manager.
- A sandbox route on your app that renders your dashboard with seeded data. This is the URL we point the demo iframe at.
Why a sandbox route?
Your real product is behind authentication. Prospects can't see it. We need one public path on your app — /demo is the convention — that renders your UI with seed data so the autonomous demo can play through it without exposing real customer state.
1
Product domain
Register the canonical URL where Incito's widget will run.
Why it matters
Incito enforces origin-level security. The widget is locked to the domain you register here. A visitor browsing a different host cannot trigger your demo, and neither can a script tag stolen from your repo and pasted on someone else's site.
Walkthrough
- In your dashboard, navigate to Setup → Profile.
- Paste the canonical product URL:
https://app.yourcompany.com. - Click Continue. You'll move to step 2.
2
Install the script tag
Drop one script tag into your app. The widget loads on demand, scoped to your registered domain.
Why it matters
The Incito widget is a single self-contained 14.6 KB gzipped script. It loads the locator engine, scenario runner, audio sync, and recorder mode in one bundle, behind a Shadow DOM so it cannot collide with your styles or globals.
The snippet
This is the canonical form. Replace ict_live_YOUR_KEY with the API key from your dashboard.
App Router: drop the script in your root layout's <body>. Pages Router: in pages/_document.tsx.
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<script
src="https://api.useincito.com/widget/v1/incito.js"
data-key="ict_live_YOUR_KEY"
async
/>
</body>
</html>
);
}
Add the tag to public/index.html just before the closing </body>. The widget waits for DOM ready on its own.
<body>
<div id="root"></div>
<script
src="https://api.useincito.com/widget/v1/incito.js"
data-key="ict_live_YOUR_KEY"
async
></script>
</body>
Paste the tag once in every HTML page you want Incito on. The widget de-duplicates itself.
<script
src="https://api.useincito.com/widget/v1/incito.js"
data-key="ict_live_YOUR_KEY"
async
></script>
Backend-rendered apps: render the snippet inside your shared layout template (Rails application.html.erb, Django base.html, Express + EJS / Handlebars layout). The bundle is HTTPS-only and async, so server framework choice doesn't matter — only that the tag ends up in every page's <body>.
<%= yield %>
<script
src="https://api.useincito.com/widget/v1/incito.js"
data-key="ict_live_YOUR_KEY"
async
></script>
The API key
Generate the key from your dashboard once at sign-up. It's shown one time. Save it to your secret manager. Previously issued keys keep working for 24 hours during rotation, so you never have downtime when you cycle.
Heartbeat verification
After you ship the script tag, the dashboard shows a live heartbeat indicator. The first time a visitor hits your site, the dashboard advances to step 3 automatically. No manual confirmation needed.
Advanced: Content-Security-Policy
If your site enforces a Content Security Policy, allow the script bundle and the WebSocket connection:
script-src 'self' 'unsafe-inline' https://api.useincito.com
connect-src 'self' https://api.useincito.com wss://api.useincito.com
Add the same two directives to whichever layer produces your Content-Security-Policy header — middleware, nginx, a CDN rule, or a meta tag. The Incito domain must be reachable for both the script bundle and the WebSocket connection.
Need this for your sandbox iframe too? See Sandbox CSP setup.
3
Open a sandbox route
Add one public path on your app — usually /demo — that renders your dashboard with seeded data.
Why we need this
Your real dashboard is behind auth, so prospects can't see it. We need a single public route on your app that renders your UI with seeded data. The widget plays the demo there. Five minutes per framework.
One sandbox route per screen your tour visits
Your widget scenario can click through sidebar navigation, modals, tabs — every destination needs its own public sandbox route (/demo, /demo/alerts, /demo/cases...), all exempt from your auth middleware. If the tour only touches one screen you can ship a single /demo page; otherwise copy the route pattern for each target.
Pick your framework
Add public /demo routes under a shared prefix, exempt them from your auth middleware, and render your existing dashboard components with seeded data. If your tour only touches one screen you can ship a single /demo page; multi-screen tours need one route per destination the scenario navigates to.
// In your existing middleware.ts, extend the public-path list.
// Using the /demo PREFIX (not exact) is important: the widget's
// recorded tour clicks through sidebar nav, and every target route
// needs to stay public.
const PUBLIC_PATHS = [
"/login",
"/signup",
"/demo", // ← matched as a prefix: /demo, /demo/alerts, /demo/cases...
];
import { DashboardView } from "@/components/dashboard/DashboardView";
// Seeded demo data. Keep generic; never mirror the shape of real
// production rows — that's how accidental PII leaks happen.
const SEED = {
alertsResolved: 603,
activeInvestigations: 14,
complianceScore: 92,
// ...match whatever props DashboardView expects
};
export default function DemoPage() {
return <DashboardView data={SEED} readOnly />;
}
import { AlertsView } from "@/components/alerts/AlertsView";
const SEED_ALERTS = [
{ id: "a1", entity: "Acme Holdings", severity: "Critical", status: "Pending" },
{ id: "a2", entity: "Greenfield Corp", severity: "High", status: "AI triaged" },
// ...keep it short and staged; not a mirror of prod
];
export default function DemoAlertsPage() {
return <AlertsView alerts={SEED_ALERTS} readOnly />;
}
export async function apiPost(url: string, body: unknown) {
if (typeof window !== "undefined" &&
window.location.pathname.startsWith("/demo")) {
// Demo route: pretend success, touch nothing.
return { ok: true, data: body };
}
return fetch(url, {
method: "POST",
body: JSON.stringify(body),
});
}
Same idea as App Router, adapted to pages/ directory. Middleware path tweak plus a pages/demo.tsx file plus the mutation guard.
const PUBLIC_PATHS = ["/login", "/signup", "/demo"];
import { DashboardView } from "@/components/DashboardView";
const SEED = {
alertsResolved: 603,
complianceScore: 92,
// ...
};
export default function DemoPage() {
return <DashboardView data={SEED} readOnly />;
}
A single controller with skip_before_action. Renders your existing dashboard view with seeded ivars.
get "demo", to: "demo#index"
class DemoController < ApplicationController
skip_before_action :authenticate_user!
def index
@alerts_resolved = 603
@compliance_score = 92
# ...assign whatever ivars your dashboard view expects
render "dashboard/show"
end
end
Mount the /demo route before auth middleware so it stays public.
app.get("/demo", (req, res) => {
const seed = {
alertsResolved: 603,
complianceScore: 92,
// ...
};
res.render("dashboard", { data: seed, readOnly: true });
});
// Your existing auth middleware runs for every route AFTER this:
app.use(authMiddleware);
One URL, one view. No @login_required decorator — that's the whole bypass.
from django.urls import path
from .views import demo_view
urlpatterns = [
path("demo/", demo_view, name="demo"),
]
def demo_view(request):
context = {
"alerts_resolved": 603,
"compliance_score": 92,
"read_only": True,
# ...
}
return render(request, "dashboard.html", context)
4
Verify the sandbox
Paste the public URL of your sandbox route. The dashboard probes it and tells you whether the iframe demo will load.
Why it matters
Two checks happen here. First we hit the URL with a HEAD request to confirm your sandbox responds publicly. Then we GET it and parse the response's Content-Security-Policy and X-Frame-Options headers — the magic-click iframe on the hosted demo landing only loads if both allow app.useincito.com. If they don't, the dashboard shows you the exact line to add.
Walkthrough
- Paste the public URL of your
/demoroute in the dashboard. - Click Verify.
- Two outcomes:
- Green pill — iframe-ready: continue to step 5.
- Amber callout — CSP needs one line: copy the framework-aware snippet, ship it to your sandbox, click re-check. Detail about the fix lives in Sandbox CSP setup.
The probe is daily. Once you're verified, Incito re-checks your sandbox CSP every 24 hours and alerts you on Slack if someone on your team accidentally tightens the policy. Drift alerts are configured in Settings → Notifications.
5
Record your first demo
Click through your sandbox once with the recorder active. Incito captures every click and input, groups them into pages, and writes the narration on top.
What happens
You click through your sandbox once with the recorder mode on. Incito captures every click and input as a Step, groups Steps into Pages, and asks Claude to write the narration on top. You review the narration in the dashboard editor, refine where you want, and click Publish. The public demo link is yours to share.
Walkthrough
- From the dashboard home, click Record first demo.
- The recorder opens your sandbox in a new tab with capture mode active.
- Click through the flow you want prospects to see. Talk to the camera if you want voice cues; the recorder captures both.
- Stop recording. The dashboard editor opens with your scenario fully transcribed.
- Edit narration, dwell timing, and on-failure behavior per Step. Click Publish when ready.
- Share the public demo URL with prospects. The widget plays the recorded scenario in their browser, narrating in real time.
You're shipped. Every visitor on your demo URL now gets the full autonomous walkthrough — cursor, narration, Q&A handoff, lead capture. The widget runs entirely on Incito's infrastructure; your stack hosts the sandbox and nothing else.
What's next
The setup is complete. These pages cover what comes after:
More guides are on the way: persona configuration (per-tenant agent voices), drift alerts (Slack notifications when a sandbox CSP regresses), CRM webhook handoff (HubSpot, Salesforce, Linear), and recorder mode advanced editing.
Email support@useincito.com for anything that's blocking. Real humans answer in business hours UTC.