Integrate a Capacitor app

The complete Capacitor 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. Capacitor install

npm i @brightmotion/agenthog-capacitor
npm i @capacitor/app @capacitor/device @capacitor/preferences   # required peers, all official plugins
npx cap sync

Requires Capacitor ≥5. Call init once at app bootstrap, before first render — vanilla TS, no framework wrapper; Ionic React/Vue/Angular all consume it the same way. Calls made before init resolves are buffered and replayed, so nothing drops.

import { AgentHog } from '@brightmotion/agenthog-capacitor'

await AgentHog.init({
  host: 'http://agenthog.io',                                // https — iOS ATS applies to native requests too
  projectKey: import.meta.env.VITE_AGENTHOG_KEY ?? '',
  enabled: !!import.meta.env.VITE_AGENTHOG_KEY,     // inert no-op without a key (dev builds)
  appName: 'myapp',
  appVersion: '1.0.0',
})

Put the key in .env as VITE_AGENTHOG_KEY=ah_xxxxxxxx (or the equivalent for your bundler). The enabled gate above keeps analytics off in dev builds and in forks that have no key.

Unlike React Native there is no storage to configure — ids and the offline queue persist through @capacitor/preferences (native storage, survives WebView data eviction). Automatic with no further work: screen views from the router (history and hash routing both hooked; trackScreens: false + AgentHog.screen('/path') to drive manually), DOM autocapture matching the web tracker (clicks with shadow-DOM-aware labels, Ionic components included, form submits, input focus, scroll depth), 30-min-idle sessions across background/foreground, and a persisted offline queue with crash carry-over.

AgentHog.capture('run_logged', { miles: 3.1 })
AgentHog.identify(email, { user_id })          // sign-in / sign-up
AgentHog.reset()                               // sign-out: new anonymous person
AgentHog.onAttribution((a) => { /* install attribution result */ })

The transport detail that matters: on a device the SDK posts through CapacitorHttp (native), so batches carry an app User-Agent — the session classifies as mobile app traffic — and no Origin header, so the project's domain allowlist never blocks a native build. A web/PWA build of the same bundle falls back to plain fetch and is treated as ordinary web traffic: correct, but a browser preview needs the preview host in the project's domains.

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 AgentHog.flagsReady()
const v = AgentHog.flag('checkout_cta')           // 'control' | 'b' | undefined
// undefined = ruleset not loaded / not enrolled / killed → code default

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

6. When events are missing

Work through this before adding more instrumentation.

SymptomCause
Sessions classify as Chrome/SafariThat was a web/PWA run of the bundle — only native builds take the CapacitorHttp path that sets the app User-Agent. On a real device build, check @capacitor/core ≥5
403 from /ingestThe web fallback hitting the domain allowlist — add the preview host to the project's domains. Native builds send no Origin and are never allowlist-blocked
Taps but no pageview: on navigationThe router changes neither the URL path nor a #/ hash — call AgentHog.screen('/path') where it navigates
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.