Capture analytics

Track product events and customer traits with the Voidhash SDK.

The SDK captures product events and sends them to Voidhash for you. Every event is attributed to the current person and delivered in background batches. You do not need a separate analytics package.

Capture an event

Call capture with an event name and any properties you want to attach:

await voidhash.capture("upgrade_button_clicked", properties: [
    "source": .string("settings"),
    "plan": .string("monthly"),
])

Use clear, stable event names. Names that begin with $ are reserved for Voidhash's own events.

Automatic events

The SDK records common lifecycle events for you, so you do not need to capture them yourself:

  • $app_installed: the first launch of this install of the app.
  • $app_updated: the first launch after the app's version or build changed.
  • $app_opened: the first time the app comes to the foreground in a process. iOS also launches your app in the background, for example for a silent push, a background fetch or a location update. Such a launch records no $app_opened until the user brings the app up.
  • $app_backgrounded: the app moved to the background.
  • $app_became_active: the app came back to the foreground after $app_backgrounded, in the same process.
  • $sign_out
  • $screen, see Track screens

To record the app events and $sign_out yourself, set automaticLifecycleEvents to false in the options. The SDK then stops capturing them, but it still notices the app coming and going: it saves queued events when the app leaves and resumes delivery when it returns.

Sessions

Every event carries a session id, so screen paths and funnels can tell one visit from the next. A session starts with the first event and ends after 30 minutes without any event, including time spent in the background. The next event then opens a new session. Signing out also starts a new session, and the session survives app restarts within the same 30 minutes. Read the current id when you need to correlate your own data with it:

let sessionId = await voidhash.sessionId()

Flush before a boundary

Events flush automatically in batches. While the app is in the foreground, a batch is sent when it reaches 20 events or every 5 seconds, whichever comes first, and failed requests are retried with exponential backoff (each retry waits longer than the last). When the server asks the SDK to slow down, the SDK waits as long as it asks, up to an hour. Force delivery when the app is about to cross an important boundary, such as a screen the customer may not return from:

await voidhash.flush()

This sends everything that is queued right now: sign-ins and customer traits first, then reported purchases, then events. Items waiting for their next retry go right away too, unless the server asked the SDK to wait or refused a reported purchase for good (see Report and restore purchases). After an outage or a rejected publishable key, flush() also checks whether the backend accepts requests again, at most once a minute.

Undelivered events

Events wait on the device until Voidhash accepts them, across app restarts and outages. The device keeps up to 1,000 undelivered events. Beyond that the oldest are dropped, which the SDK reports once per flush as the ANALYTICS_EVENT_DROPPED diagnostic. When Voidhash refuses an event as invalid, only that event is dropped; the events sent with it are still delivered.

Each event records the app version and build, OS version, locale, and time zone at the moment it was captured. An event sent after an app update still counts towards the release it happened in.

Update customer traits

Trait updates are saved on the device and sent separately from analytics events, so they are never dropped to make room for events. See Set customer attributes for how to set them.

When an end user declines usage analytics, or has not decided yet, tell the SDK:

await voidhash.setAnalyticsConsent(.denied) // .granted, .denied or .pending
let consent = voidhash.analyticsConsent

denied and pending behave the same. While consent is either of them:

  • The SDK captures no custom events, screens, app lifecycle events, $sign_out, or paywall impressions and interactions. Nothing is queued, stored on the device, or sent, and no analytics session is kept. $app_installed and $app_updated are recorded once consent is granted.
  • Person attributes are neither sent nor queued, and setPersonAttributesState returns .skipped.
  • The first analytics call the SDK skips is reported once as the ANALYTICS_CONSENT_DENIED diagnostic.
  • Purchases, restores, entitlements, feature flags, and paywalls work as usual. identify still links the customer and sends the email and name you pass, because purchases are attributed to them.
  • Requests to Voidhash report the missing consent in the x-analytics-consent header, so Voidhash records no paywall exposure for them and discards any analytics that still arrive.

Withdrawing consent also discards the events still waiting on the device, including a batch that is being sent, and ends the analytics session. Granting it collects from that moment on; nothing skipped in the meantime is sent later.

The analyticsConsent option sets the consent at launch and defaults to .granted, so existing apps keep collecting analytics. Set it to .pending when your app asks for consent first. The choice you set at runtime is stored on the device and takes precedence over the option on later launches. Signing out and resetting keep it, so ask again only when your own consent flow requires it.

Revenue reporting comes from purchases and is unaffected. Metrics built from analytics events, such as paywall impressions and conversion rates, count only end users who granted consent.

Next steps