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() or reset() signed a signed-in user out. |
$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() and reset()
also start a new one when they act. 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. - Install attribution doesn't run.
- Purchases, restores, perks, feature flags and paywalls work.
- What your app says about the person is still sent:
identify()with its email and name, person attributes, and groups with their properties.
Analytics consent covers what the SDK observes, not what your app says about a person. Feature
flags and cohorts can target a person's attributes and groups, so those must not depend on whether
the person agreed to tracking. To send nothing before consent, don't call identify(),
setPersonAttributes(), group() or setGroups() until then, or create the client with
enabled: false.
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.
Keep events until the person answers
By default, what happens while consent is pending is dropped, so the first screens of a person
who then agrees are missing. With analyticsWhilePending: "buffer", the SDK holds those events in
memory and records them when the person agrees:
import { createVoidhashClient } from "@voidhash/react-native";
export const voidhash = createVoidhashClient("vh_pk_...", {
analyticsConsent: "pending",
analyticsWhilePending: "buffer",
});
// When the person answers your consent dialog:
await voidhash.client.setAnalyticsConsent("granted"); // or "denied"- Events, screens and lifecycle events are held, each with the time it happened. Person attributes and group descriptions are not held: they are sent at once.
- Held events stay in memory only. Nothing is stored or sent before the answer, and closing or restarting the app before the answer loses them.
- At most 100 events are held; after that the oldest is dropped.
grantedrecords$app_installedor$app_updated, if still unreported, and then the held events, in order and with their original times, for the identity, groups and session in effect at that moment.denieddiscards them.deniednever holds anything, and"drop"remains the default.
Let sign-in grant consent
If signing in to your app accepts terms that cover analytics, set grantConsentOnIdentify instead
of showing a dialog to signed-in users. identify() then grants a consent that is still pending:
import { createVoidhashClient } from "@voidhash/react-native";
export const voidhash = createVoidhashClient("vh_pk_...", {
analyticsConsent: "pending",
analyticsWhilePending: "buffer",
grantConsentOnIdentify: true,
});- The grant happens before the link, so events held with
"buffer"are recorded for the anonymous person thatidentify()then links to your user. deniedis never changed: a person who declined stays declined after signing in.- The grant lasts as long as the sign-in, also across launches.
signOut()andreset()first send what was recorded, then return consent topending. - Any
setAnalyticsConsent()call makes the choice the person's own.signOut()andreset()then keep it.
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 needs analytics consent; without it, the agreement is left out. 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.