Capture analytics
Record events, crashes and opened notifications, and respect analytics consent.
The SDK records events for the current person and sends them in batches, so you need no separate
analytics package. Events, built-in $ events and properties are described in
Events.
Capture an event
voidhash.client.capture("upgrade_button_clicked", {
source: "settings",
plan: "monthly",
});capture() is synchronous and works from the first render, before the provider has initialized.
Properties are copied at capture time and converted to JSON: a bigint becomes a decimal string, a
Date an ISO string, and NaN or Infinity null. Functions, undefined and circular
references are left out. An event with more than 10,000 property values is dropped and reported as
ANALYTICS_EVENT_DROPPED, so pass the fields you need, not framework objects.
Each event records the app version, OS version and locale at capture time. Names starting with $
are reserved.
Automatic events
| Event | When |
|---|---|
$app_installed, $app_updated | First launch of a new install or version. |
$app_opened | A launch the user sees. A background launch records it at the first activation instead. |
$app_became_active, $app_backgrounded | The app enters or leaves the foreground. |
$app_crashed | The app crashed in an earlier run. See Crashes. |
$sign_out | signOut() was called. |
$screen | A new screen appeared. See Track screens. |
$push_opened | You reported an opened notification. See Report opened notifications. |
Set automaticLifecycleEvents: false to stop the $app_* events and $sign_out.
Crashes
The SDK reads the crash records iOS and Android keep, after the next launch. It installs no crash
handler, so it doesn't interfere with your crash reporter. $app_crashed carries crash_kind and
$app_version, and no stack trace.
| Platform | crash_kind | $app_version |
|---|---|---|
| iOS | crash | The version that crashed. iOS may not hand over every report. |
| Android 11 (API 30) and later | crash, native_crash or anr | The version running when the crash is reported. |
Android 10 and earlier report no crashes. A crash older than 24 hours is dated 24 hours back.
Report opened notifications
Report an opened notification from your notification library's handler. The SDK records
$push_opened for notifications Voidhash sent, which carry a voidhash_send_id data key, and
ignores others. See Push notifications.
import * as Notifications from "expo-notifications";
Notifications.addNotificationResponseReceivedListener((response) => {
voidhash.client.trackPushOpened(response.notification.request.content.data);
});import messaging from "@react-native-firebase/messaging";
messaging().onNotificationOpenedApp((message) => {
voidhash.client.trackPushOpened(message.data);
});
const initial = await messaging().getInitialNotification();
voidhash.client.trackPushOpened(initial?.data);Reporting the same notification twice in a row counts once.
Sessions and delivery
A session ends after 30 minutes without events, background time included; signOut() also starts a
new one. Read it with voidhash.client.getSessionId(). It returns undefined before init(),
without analytics consent, and once the session has timed out.
Events are sent in batches of 20 or every 5 seconds. Failed requests retry with backoff until Voidhash accepts or refuses the event. Up to 1,000 events wait on the device; the oldest is dropped first. Force delivery before an important boundary:
const result = await voidhash.client.flush();
if (result.isOk()) console.log(result.value.pending); // events still queuedIdentity methods flush on their own, so events stay with the identity that captured them.
Analytics consent
When your app asks for consent first, start with "pending" and record the choice:
import { createVoidhashClient } from "@voidhash/react-native";
export const voidhash = createVoidhashClient("vh_pk_...", { analyticsConsent: "pending" });
await voidhash.client.setAnalyticsConsent("granted"); // or "denied"
voidhash.client.getAnalyticsConsent(); // "granted" | "denied" | "pending"While consent is denied or pending:
- No events, screens, lifecycle events,
$sign_outor paywall impressions are captured, stored or sent, and no session is kept.ANALYTICS_CONSENT_DENIEDis reported once. - Person attributes aren't stored;
setPersonAttributesSync()answersskipped. - Install attribution doesn't run.
- Purchases, restores, perks,
identify()(with email and name), feature flags and paywalls work.
Withdrawing consent discards the events still on the device. Granting it collects from then on;
$app_installed or $app_updated not yet reported is reported then. The choice is stored on the
device, takes precedence over the option on later launches, and survives signOut() and reset().
If it can't be stored, setAnalyticsConsent() returns FAILED_TO_SET_ANALYTICS_CONSENT and still
applies for this run.
Revenue comes from purchases and is unaffected. Paywall impressions and conversion rates count only people who granted consent.
Share purchases with ad platforms
Voidhash sends a person's purchases to an ad platform's Conversions API only after they agree. Record that separately from analytics consent:
await voidhash.client.setAdDataSharing(true); // agreed
await voidhash.client.setAdDataSharing(false); // withdrawntrue is a person attribute and needs analytics consent. false is always sent, even without
consent, and retried for up to 48 hours. When someone rejects everything, call both
setAdDataSharing(false) and setAnalyticsConsent("denied").
Install attribution
On a new install, the SDK records the ad that brought the person as their first ad touch. It runs once, in the background, only with analytics consent, and retries on up to 10 launches.
- iOS asks Apple Ads for the campaign, ad group and ad. It needs no tracking prompt.
- Android reads the Google Play install referrer:
utm_*parameters, click IDs such asgclidandfbclid, and campaign, ad set and ad IDs.
On Android, set metaAppId to also ask the Facebook and Instagram apps on the device; Voidhash then
needs your Meta install referrer decryption key. Turn the
feature off with installAttribution: false. Installs that ran an earlier SDK version aren't read.
See Ad attribution in your app.
Send events from elsewhere
Your backend can send events with the Event Capture API, authenticated with the publishable key.
Next steps
Something wrong or missing on this page? Tell us by email or on Discord.