Skip to content

Analytics integration

PostHog and Wire AI on React Native

Wire AI emits a typed lifecycle event on every turn of the flow, and toAnalyticsEvent maps each one to a canonical wire_onboarding_* name so the same funnel reads identically in every app you ship. Piping that into PostHog is one prop. The part that catches people is the last step: there is no completed event on onEvent, because the kit signals completion through onComplete instead. Wire only onEvent and PostHog shows a funnel that starts and never finishes, which reads as a total drop on every cohort.

Install

terminal
$
npxexpo install posthog-react-native expo-file-system expo-application expo-device expo-localization
$
npminstall @wireai/activation

API surface on this page read against @wireai/activation 0.13.5 (npm latest, read 2026-08-04) and posthog-react-native 4.61.2 (npm latest, read 2026-08-02). These are the versions the calls below were checked against, not a claim that this exact file is running in production somewhere.

Map the kit events to canonical names

toAnalyticsEvent takes the kit's OnboardingEvent union and returns a name plus params. The switch is exhaustive over that union on purpose, so a new event type becomes a compile error in the kit rather than a silent gap in your dashboard.

WIRE_ONBOARDING_EVENTS.completed is a constant with no matching kit event. Capture it from onComplete, using the same constant, and the terminal step keeps the canonical name instead of whatever you would have typed by hand at 1am.

components/OnboardingScreen.tsx
1import { usePostHog } from "posthog-react-native";
2import {
3  WireOnboarding,
4  toAnalyticsEvent,
5  WIRE_ONBOARDING_EVENTS,
6} from "@wireai/activation";
7
8import { wireConfig } from "@/lib/wire";
9import { StaticOnboarding } from "@/components/StaticOnboarding";
10
11export function OnboardingScreen({ onDone }: { onDone: () => void }) {
12  const posthog = usePostHog();
13
14  return (
15    <WireOnboarding
16      config={wireConfig}
17      onEvent={(event) => {
18        const mapped = toAnalyticsEvent(event);
19        posthog.capture(mapped.name, mapped.params);
20      }}
21      onComplete={(result) => {
22        // No kit event for this one. Capture it or the funnel has no end.
23        posthog.capture(WIRE_ONBOARDING_EVENTS.completed, {
24          answers: Object.keys(result.answers).length,
25        });
26        onDone();
27      }}
28      fallbackFlow={<StaticOnboarding onDone={onDone} />}
29    />
30  );
31}

Build the funnel on started OR resumed

Pass the storage prop and the kit persists its session seed, so an app kill mid-onboarding resumes the same backend session rather than minting a new one. When that happens the kit emits resumed instead of started, deliberately, so a host funnel does not double-count one session as two starts.

The consequence for PostHog is that a funnel whose first step is only wire_onboarding_started undercounts. Use both names for the entry step and the completion rate stops drifting on the cohort of people whose phone rang mid-flow.

components/OnboardingScreen.tsx (the storage prop)
1import AsyncStorage from "@react-native-async-storage/async-storage";
2import { WireOnboarding } from "@wireai/activation";
3
4import { wireConfig } from "@/lib/wire";
5
6export function OnboardingScreen({ onDone }: { onDone: () => void }) {
7  return (
8    <WireOnboarding
9      config={wireConfig}
10      // storage turns an app kill into "resumed",
11      // not a phantom second "started"
12      storage={AsyncStorage}
13      onComplete={onDone}
14    />
15  );
16}

What breaks

The failure modes when PostHog and Wire AI share an app. None of them throws, which is why they survive code review.

onEvent has no completed case

The kit signals completion through onComplete, not onEvent, so toAnalyticsEvent will never hand you wire_onboarding_completed. Capture it explicitly from onComplete with the exported constant. Every funnel that looks like a 100% drop-off is this bug.

resumed fires instead of started

With storage set, a restored session emits resumed and skips started on purpose. Count both as the funnel entry or your completion rate is quietly lower than reality.

userId is capped at 128 characters and must be pseudonymous

The kit trims and truncates at 128 rather than rejecting, so an over-long id gets silently cut and stops matching your user table. It is an opaque id you own, never an email, a name, or a phone number.

The AI can degrade, and degrading is an event

retry and fallback both come through onEvent with a reason of backend or timeout. Those are the two lines that tell you whether a bad activation week was the flow or the model. Chart them next to the funnel, not in a separate dashboard nobody opens.

Questions people actually ask

Do I still need Wire AI analytics if PostHog is already in the app?

The kit reports its own funnel to the Wire server because the experiment engine needs those events to assign and score arms. Sending the same moments to PostHog costs one prop and keeps your existing dashboards whole.

Can I use my PostHog distinct id as the Wire user id?

Yes, as long as it is pseudonymous and inside 128 characters. The userId prop also binds late: change it mid-session after the user registers and the kit emits an identify event against the live session.

Does this add a second analytics SDK to my bundle?

No. toAnalyticsEvent and the event constants are plain functions and strings. Your transport stays PostHog.

Sources

Wire it into your app

Analytics is free to 299k events a month, forever, on any app. 15 founder seats get the whole engine free, 4 filled.

Written by Malik Chohra. 9 years React Native. Shipped an app at 9M monthly users, and took a consumer app from a 4.3 to a 4.9 App Store rating.

Next: Expo Router · RevenueCat