Integrate a website
The complete web path, top to bottom. Covering every platform in one document instead — that's the full guide.
1. Get a project key
Every install needs a key shaped ah_xxxxxxxx. Create one at /projects/new, or run ah projects list if the CLI is already authenticated. Do not invent a key and do not proceed without one — the SDKs disable themselves without a key.
One project covers every platform a single product ships on — website, mobile app, and game share the same key and land in the same dashboard. Use a separate project only for a separate product, so their stats stay clean.
2. Web install
One script tag, before the closing head tag, on every page:
<script src="http://agenthog.io/ah.js" data-project="ah_xxxxxxxx" defer></script>
Add it where the framework renders <head> site-wide — app/layout.tsx for the Next.js App Router, pages/_document.tsx for the Pages Router, app/root.tsx for React Router or Remix, the shared src/layouts/*.astro for Astro. Never per-page.
Optional attributes:
| Attribute | When you need it |
|---|---|
data-api | Self-hosted backend on a different origin than the script itself |
data-cookie-domain | Share one visitor id across subdomains — set to a registrable domain like .example.com so app. and www. resolve to the same person |
With no further work you get pageviews (SPA route changes included — pushState, replaceState, and popstate are hooked), clicks, form submits, input focus, scroll depth at 25/50/75/90/100%, and a leave event carrying time-on-page and max scroll.
For everything autocapture cannot see, the browser global is:
window.agenthog.capture('checkout_completed', { plan: 'pro', cents: 4900 })
window.agenthog.identify('[email protected]', { plan: 'pro' }) // email = cross-device stitch key
window.agenthog.tag('beta_cohort', true)
window.agenthog.register({ app_version: '2.1.0' }) // merged into every later eventIn an SSR framework, guard those behind typeof window !== 'undefined' && window.agenthog — the object exists only in the browser, only after the script runs.
3. Event naming is a contract
The CLI, funnels, and dashboard parse these exact shapes. Autocapture already emits them; match the style when you add your own.
pageview: /pricing click: Join the waitlist form_submit: waitlist input: email scroll: 75% leave: /pricing
- · Custom names are stored verbatim. Short, readable, stable —
checkout_completed, notevt_CHECKOUT_v2or a UUID. - · Keep varying values out of the name.
capture('plan_selected', { plan: 'pro' }), nevercapture('plan_selected_pro')— props are queryable with--by props.plan, names baked with values are not. - ·
identify(email)is what stitches a person across devices. Call it at sign-in and after sign-up. A bare id works but will not merge across devices. - · Never put secrets, tokens, or full PII blobs in props — anyone with dashboard access can read them.
4. Verify before reporting success
Compiling is not evidence. Load the site or run the app, click through a few screens, then confirm data arrived:
ah events --since 24h # fastest proof, if the CLI is authenticated ah digest # sessions, sources, and top events in one report
Otherwise check the dashboard. Expect a pageview: row within seconds — the web tracker flushes every 5s or 10 queued events; React Native, Unity, and Capacitor every 10s or 20 events. Unity editor Play mode sends real events too (registered prop platform: editor).
5. Feature flags & A/B tests
Create a flag with ah flags create checkout_cta --variants control:50,b:50, then read it — the SDK assigns the variant deterministically per user and records exposure automatically (one $exposure event, plus $ff/checkout_cta on every later event, so ah funnel signup --by flag:checkout_cta just works):
await window.agenthog.flagsReady() // optional: skip the first-visit undefined window
const v = window.agenthog.flag('checkout_cta') // 'control' | 'b' | undefined
if (v === 'b') renderNewCheckout()
else renderOldCheckout() // control AND fallback pathFull loop — experiments, results, verdict gates, rollout percentages, kill switch: Experiments.
6. When events are missing
Work through this before adding more instrumentation.
| Symptom | Cause |
|---|---|
Console: missing data-project | No data-project attribute, or it rendered as an empty template value |
No /ingest requests at all | Ad blocker, a CSP script-src, or the tag never rendered — check the built HTML, not the source |
| Only the first page tracks | The tag went on one page instead of the site-wide layout |
| Every session is a new visitor | Cookies and localStorage blocked (private mode, aggressive privacy settings) |
| Traffic looks inflated | You are looking at bots — aggregates exclude them by default, --all includes them |
7. After integrating
Two follow-ups worth offering:
- · Define a conversion goal so sessions get marked converted:
ah goals set signup "form_submit: waitlist" - · Give the agent the read surface — install the ah CLI and add an AgentHog section to
CLAUDE.mdso future sessions query analytics instead of guessing.