Observer Mode & Debugging
Run Voidhash alongside another billing SDK with read-only observer mode, and debug integrations with HTTP logging and error swallowing.
Voidhash can run as the SDK that owns your purchases, or as a passive observer next to another
billing SDK. This page covers the readOnly observer mode and the two debug toggles: debug
HTTP logging and the unstable_swallowErrors alpha flag.
Observer Mode
If another billing SDK (or your own StoreKit / Play Billing code) currently owns purchases in
your app, enable observer mode with readOnly: true. Voidhash then watches store transactions
and syncs them to the server, but never initiates purchases and never acknowledges or finishes
transactions — that stays the responsibility of whichever SDK made the purchase.
import { createVoidhashClient } from "@voidhash/react-native";
export const voidhash = createVoidhashClient("vh_pk_...", {
readOnly: true,
scheme: "myapp",
});| Mode | Starts purchases | Syncs transactions to Voidhash | Acknowledges/finishes store transactions |
|---|---|---|---|
readOnly: true | No | Yes | No |
readOnly: false (default) | Yes | Yes | Yes (after successful server sync) |
Because observed transactions still sync, person data stays
accurate: entitlements and subscription status from useCurrentPerson() reflect purchases made
through the other SDK.
What is blocked
Methods that write on behalf of the user throw ReadOnlyModePurchaseNotAllowedError in observer
mode:
client.purchase(...)— and by extension theusePurchasehook.client.setPersonAttributesSync(...)— it performs a network write, mirroringpurchase. The fire-and-forgetsetPersonAttributes(...)is unaffected.
Everything else works normally: identify, getProducts / useProducts, feature flags,
analytics capture, and restorePurchases() (which reconciles observed transactions and refreshes
the person, without acknowledging anything).
How observed transactions are synced
- The SDK attaches the native purchase listener first during
init(), so purchases completed by the other SDK are observed as they happen. - A background reconciliation pass also runs after init — it does not block
init(). - Reconciliation sources are pending transactions and active purchase history from the store.
Consumable products (
one-time-consumable) are skipped during reconciliation. - The client dedupes by
platform + transactionId + purchaseDate, and the server sync endpoint is idempotent by store transaction identity, so retries and duplicates are tolerated. - Android transactions without a purchase token are skipped.
Acknowledge-after-sync in normal mode
Outside observer mode, Voidhash acknowledges/finishes a store transaction only after the server has accepted the sync. In observer mode this step is skipped entirely — if nothing in your app finishes the transaction, the store will eventually refund it, so make sure the owning SDK still does its job.
HTTP Debug Logging
Enable verbose HTTP logging when debugging request/response flow between the SDK and the Voidhash API:
export const voidhash = createVoidhashClient("vh_pk_...", {
debug: true,
scheme: "myapp",
});Every request the SDK makes is logged to the console under the [voidhash:http] tag:
- Requests: method, URL, headers, and a body summary (text-like bodies are previewed, binary payloads are not decoded).
- Responses: status, headers, and request duration in milliseconds.
- Errors: the failure reason and HTTP status when available.
Sensitive header values are redacted before logging — any header whose name matches
authorization, cookie, secret, password, token, or api-key is replaced with
[REDACTED]. Header values and body previews are also truncated, so large payloads don't flood
your logs.
Development only
Debug logs still include request URLs, non-sensitive headers, and body previews. Keep
debug: true out of production builds.
Swallowing Errors (Alpha)
For early-alpha integrations — typically observer-style setups where Voidhash runs in the background and must never crash a flow it doesn't own — you can enable unstable side-effect error swallowing:
export const voidhash = createVoidhashClient("vh_pk_...", {
readOnly: true,
scheme: "myapp",
unstable_swallowErrors: true,
});With unstable_swallowErrors: true, side-effect methods log a
[voidhash] swallowed error in <operation> warning instead of rejecting:
init()end()identify(...)reset()signOut()setPersonAttributes(...)restorePurchases()flush()iosPresentCodeRedemptionSheet()iosShowManageSubscriptions()
Methods that return data you act on remain strict and still reject on failure, regardless of the flag:
getCurrentPerson(...)setPersonAttributesSync(...)getDistinctId()getFeatureFlags(...)getPaywallForLocation(...)getProducts()purchase(...)
Intentionally unstable
The unstable_ prefix is honest — this behavior may change or disappear in any release. It is
meant for background/observer-style alpha integrations, not for apps where Voidhash handles the
core purchase flow. If you rely on catching errors, see
Error Handling for the error shapes the SDK throws.
For the full list of client options, see Configuration.