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:

AttributeWhen you need it
data-apiSelf-hosted backend on a different origin than the script itself
data-cookie-domainShare 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 event

In 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, not evt_CHECKOUT_v2 or a UUID.
  • · Keep varying values out of the name. capture('plan_selected', { plan: 'pro' }), never capture('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 path

Full loop — experiments, results, verdict gates, rollout percentages, kill switch: Experiments.

6. When events are missing

Work through this before adding more instrumentation.

SymptomCause
Console: missing data-projectNo data-project attribute, or it rendered as an empty template value
No /ingest requests at allAd blocker, a CSP script-src, or the tag never rendered — check the built HTML, not the source
Only the first page tracksThe tag went on one page instead of the site-wide layout
Every session is a new visitorCookies and localStorage blocked (private mode, aggressive privacy settings)
Traffic looks inflatedYou 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.md so future sessions query analytics instead of guessing.