React Native SDK reference
Client options, provider state, hooks, client methods, caching and diagnostics of @voidhash/react-native.
Everything createVoidhashClient accepts and the client exposes. To install the SDK, see
React Native SDK.
Client options
| Option | Default | Description |
|---|---|---|
dev | false | Simulated purchases in debug builds; ignored in release builds. See Development mode. |
debug | false | Logs HTTP requests and responses, with sensitive values redacted. |
enabled | true | false makes the client inert. Fixed at construction. See Disabled clients. |
readOnly | false | Observer mode. See Observer mode. |
distinctId | Stored or new anonymous ID | Seeds the identity on the first init() only. Usually omit it and call identify(). |
analyticsConsent | "granted" | "denied" or "pending" stops analytics. See Analytics consent. |
analyticsWhilePending | "drop" | "buffer" holds events captured while consent is "pending" in memory until the answer. See Analytics consent. |
grantConsentOnIdentify | false | true makes identify() grant a "pending" consent. See Analytics consent. |
automaticLifecycleEvents | true | Captures $app_* events and $sign_out. |
installAttribution | true | Credits installs to the ad behind them. See Install attribution. |
metaAppId | none | Android only: also asks Meta's apps about the install. |
screenTracking | { enabled: true } | Automatic $screen events. See Track screens. |
flowCapabilities | ["links"] | What paywalls may do beyond purchasing. See Flow capabilities. |
preloadPlacements | [] | Location slugs whose paywalls download at launch. |
onDiagnostic | none | Receives failures the SDK recovered from. See Diagnostics. |
baseUrl | https://api.voidhash.com | API origin for self-hosted deployments. |
ingestUrl | Origin of baseUrl | Analytics origin only. |
baseUrl and ingestUrl must be absolute http:// or https:// URLs; blank counts as unset, and
whitespace around the value, such as a trailing newline from an environment file, is ignored. For
an invalid URL, development builds throw INVALID_ARGUMENT; release builds use the default origin
and report INVALID_CONFIGURATION through onDiagnostic.
scheme, connectivity and unstable_nativeEngine are deprecated and have no effect.
unstable_swallowErrors is deprecated; use onDiagnostic.
Disabled clients
The client is inert with enabled: false, without a publishable key (null, undefined or
blank), and on a platform other than iOS and Android. It never touches the store or the network.
Methods resolve as no-ops and reads return their empty shape, so a build or an environment without
a key needs no code around the SDK:
import { createVoidhashClient } from "@voidhash/react-native";
export const voidhash = createVoidhashClient(process.env.EXPO_PUBLIC_VOIDHASH_KEY);Mount the provider either way, so hook order matches in every build. To enable the SDK later, create a new client.
Observer mode
With readOnly: true the SDK records purchases that another billing SDK makes, but never starts,
finishes or acknowledges them. purchase() returns READ_ONLY_PURCHASE_NOT_ALLOWED; everything
else works. Switch at runtime without a new client:
voidhash.client.setReadOnly(false); // Voidhash now owns purchases
voidhash.client.isReadOnly; // falseA purchase already in flight keeps the mode it started in. Report the other SDK's purchases with
reportTransaction. See
Observer mode for the migration plan.
Provider
Mount <voidhash.Provider> above your app. It initializes identity, the project schema, the
person, the store connection and the transaction observer. With several React roots, mount a
provider in each; they share one client, which stops when the last provider unmounts.
useVoidhash() returns the provider state:
| Field | Type | Description |
|---|---|---|
status | "initializing" | "ready" | "failed" | "disabled" | Lifecycle state. |
initError | VoidhashError | null | Set only while status is failed. |
retryInit | () => void | Runs init() again. Does nothing unless status is failed. |
isInitialized | boolean | status === "ready". |
client | VoidhashClient | The imperative client. |
Initialization doesn't wait for the network: an offline cold start initializes from device state.
It fails when the binary lacks the SDK's native modules, so handle failed:
const { status, initError, retryInit } = voidhash.useVoidhash();
if (status === "failed") return <RetryScreen error={initError} onRetry={retryInit} />;An app launched in the background before the first unlock after a reboot stays initializing
until the device is unlocked. Events captured meanwhile are kept; on Android, the 100 most recent.
Android starts an app that early only when it declares a directBootAware component.
Hooks
Hooks are client properties, such as voidhash.useHasPerk. Data hooks report isLoading: true
until the provider initializes and their first read answers, and false for a disabled or failed
provider. error is null when there is none. After identify() or reset(), mounted hooks
read again for the new person.
| Hook | Returns | Guide |
|---|---|---|
useHasPerk(perkSlug) | { hasAccess, grant, isLoading, isStale, error, refetch } | Check access |
useCurrentPerson() | { data, error, isLoading, isStale, refetch } | Check access |
useSubscriptions() | { all, current, error, isLoading, isStale, refetch } | Purchases |
usePresentFlow(placement) | { present, isPresenting } | Display a paywall |
useProducts() | { products, data: { get, toList, bySlug }, error, isLoading, refetch } | Purchases |
useProducts(slugs) | { products, error, isLoading, refetch }, products in the order of slugs | Purchases |
usePurchase(options?) | { purchase, pendingProduct, isLoading, error } | Purchases |
useRestorePurchases(options?) | { restore, isLoading, error }; restore() answers the person | Purchases |
useFeatureFlags(keys?) | { isEnabled, getVariant, data, error, isLoading, refetch } | Feature flags |
useVoidhash() | Provider state | Provider |
Client methods
voidhash.client exposes the same workflows outside React. Methods that can fail resolve to a
Result and never reject; see Errors.
| Method | Description |
|---|---|
hasPerk(slug, { forceFetch? }) | Checks one perk, with freshness. |
getCurrentPerson({ forceFetch? }) | Returns { person, isStale, isExpired, reason }. |
getProducts(slugs?) | Every product, or those of slugs in their order. |
purchase(product, { offer?, quantity?, replacing?, appStore?, googlePlay? }) | Buys a product; a subscription with its free trial unless offer says otherwise. |
restorePurchases() | Restores purchases for the current person and answers the refreshed person. |
addTransactionListener(listener) | Hears about purchases that complete outside purchase(); returns the unsubscribe function. |
syncPurchases() | Scans the store and sends missing receipts without prompting. |
reportTransaction(transaction, { isRestore? }) | Reports another SDK's purchase in observer mode. |
presentFlow(placement) / dismissFlow() | Presents or closes the paywall at a location. |
getPaywallForLocation(slug) | What is shown at a location, or null, without presenting it. |
getFeatureFlags(keys?) | Evaluates flags. |
identify(userId, { email?, name? }) / identifySync(...) | Identifies the person. |
signOut() / reset() | Signs the user out. When nobody is signed in, signOut() does nothing and reset() starts a fresh anonymous identity. |
getDistinctId() | The current distinct ID. |
setPersonAttributes(attrs) / setPersonAttributesSync(attrs) | Sets person attributes. |
setAdDataSharing(granted) | Records consent to share purchases with ad platforms. |
capture(name, props?) / screen(name, props?) | Records an event or a screen. |
trackPushOpened(data) | Records an opened Voidhash notification. |
group(type, key, props?) / setGroups(groups) / resetGroups() / getGroups() | Puts the person in a group, replaces all their groups, takes them out, or reads them. |
setAnalyticsConsent(consent) / getAnalyticsConsent() | Sets or reads analytics consent. |
getSessionId() | The current analytics session ID, or undefined without analytics consent or a live session. |
flush() | Sends queued writes, receipts and events now. |
resetCache() | Clears the cached person: hooks show no person until the next read fetches it. |
setReadOnly(readOnly) | Switches observer mode. |
showManageSubscriptions(product?) / presentCodeRedemption(code?) | The store's subscription management and offer code redemption. |
getStorefront() / canMakePayments() | The person's store country and currency, and whether they can buy. |
appStore.* / googlePlay.* | What only one store has, such as appStore.beginRefundRequest(transactionId) or googlePlay.showInAppMessages(); UNSUPPORTED_PLATFORM on the other platform. |
init() / end() | Starts or stops the client. The provider calls both for you. |
end() flushes for at most about 2 seconds, saves what's still queued and stops every timer and
listener; the queue is sent after the next init(). Events captured after end() are dropped. The
client stops being initialized as soon as end() is called: client.isInitialized is false and
reads return VOIDHASH_CLIENT_NOT_INITIALIZED from then on.
Package entry points
| Import | Use |
|---|---|
@voidhash/react-native | Client, provider, hooks, types, isAnonymousDistinctId |
@voidhash/react-native/expo-router | ScreenTracking, useExpoRouterScreenTracking |
@voidhash/react-native/react-navigation | useScreenTracking |
@voidhash/react-native/metro | withVoidhash for Metro |
@voidhash/react-native/app.plugin.js | The Expo config plugin |
Caching and offline behavior
Reads answer from the device cache first and refresh in the background, so they don't fail offline.
| Data | Fresh for | Kept for |
|---|---|---|
| Person and perks | 5 minutes | 2 days, then isExpired |
| Feature flags | 5 minutes | Until replaced |
| Paywall per location | 1 hour | 7 days |
A stale read waits at most 500 ms for a refresh, then answers from cache. An uncached read waits for the request, about 10 seconds. The SDK refreshes at launch, on foreground and on reconnect (each at most once a minute), after purchases and restores, and on identity changes.
Events, person writes and store receipts are stored on the device and retried until Voidhash accepts them. Only the event queue is capped: 1,000 events, oldest dropped first. Receipts are never dropped.
Diagnostics
onDiagnostic reports problems the SDK recovered from, for logging and alerts:
import { createVoidhashClient } from "@voidhash/react-native";
createVoidhashClient("vh_pk_...", {
onDiagnostic: (diagnostic) => {
if (diagnostic.kind === "auth") reportToMonitoring(diagnostic);
},
});| Field | Meaning |
|---|---|
kind | transport, eviction, breaker, auth, cache, configuration or consent |
code | Stable code, such as AUTHENTICATION_FAILED or TRANSACTION_REJECTED |
operation | The SDK operation, such as capture |
retryable | Whether the SDK retries on its own |
httpStatus | Present when there was a response |
message | Human-readable detail; don't parse it |
A rejected publishable key is reported once as AUTHENTICATION_FAILED. The refused traffic pauses
and stays queued; one request is let through every 60 seconds. Only a 401 or 403 that Voidhash
itself answered counts: one from a proxy or firewall in front of Voidhash is a temporary failure
and pauses nothing. In the same way, only an answer from Voidhash itself lifts the pause, not a
proxy's page sent with 200.
Next steps
Something wrong or missing on this page? Tell us by email or on Discord.