VoidhashDocs
DevelopersHelp centerGuidesAPI reference
Dashboard
Get started
Introduction
Quickstart
How Voidhash works
Glossary
Versions and releases
Concepts
Teams, projects and environments
API keys
Products and perks
Offers
Subscription lifecycle
People and identity
Groups
Events
Features
Paywalls in your app
Feature flags and A/B tests
Paywall components
Push notifications
Ad attribution in your app
Client SDKs
React Native
Swift
Kotlin
Web
Your backend
Node.js
PHP
Go
Rust
Other languages
Payment sources
Forward store notifications
Developer tools
CLI
MCP server
Query analytics with VoidQL
React Native SDK
React Native quickstart
Display a paywall
Make and restore purchases
Check access
Identify users
Evaluate feature flags
Capture analytics
Groups
Track screens
Development mode
React Native SDK reference
Errors
Troubleshooting

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

EventWhen
$app_installed, $app_updatedFirst launch of a new install or version.
$app_openedA launch the user sees. A background launch records it at the first activation instead.
$app_became_active, $app_backgroundedThe app enters or leaves the foreground.
$app_crashedThe app crashed in an earlier run. See Crashes.
$sign_outsignOut() was called.
$screenA new screen appeared. See Track screens.
$push_openedYou 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.

Platformcrash_kind$app_version
iOScrashThe version that crashed. iOS may not hand over every report.
Android 11 (API 30) and latercrash, native_crash or anrThe 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.

expo-notifications
import * as Notifications from "expo-notifications";

Notifications.addNotificationResponseReceivedListener((response) => {
  voidhash.client.trackPushOpened(response.notification.request.content.data);
});
@react-native-firebase/messaging
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 queued

Identity 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_out or paywall impressions are captured, stored or sent, and no session is kept. ANALYTICS_CONSENT_DENIED is reported once.
  • Person attributes aren't stored; setPersonAttributesSync() answers skipped.
  • 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); // withdrawn

true 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 as gclid and fbclid, 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

  • Track screens
  • Identify users
  • Check your data in Studio

Something wrong or missing on this page? Tell us by email or on Discord.

PreviousEvaluate feature flagsNextGroups

On this page

Capture an eventAutomatic eventsCrashesReport opened notificationsSessions and deliveryAnalytics consentShare purchases with ad platformsInstall attributionSend events from elsewhereNext steps