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, 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 AgentHog.flagsReady()
const v = AgentHog.flag('checkout_cta') // 'control' | 'b' | undefined
// undefined = ruleset not loaded / not enrolled / killed → code defaultFull loop — experiments, results, verdict gates, rollout percentages, kill switch: Experiments.
6. When events are missing
Work through this before adding more instrumentation.
| Symptom | Cause |
|---|---|
| Sessions classify as Chrome/Safari | That 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 /ingest | The 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 navigation | The router changes neither the URL path nor a #/ hash — call AgentHog.screen('/path') where it navigates |
| 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.