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.

utils/voidhash/client.ts
import { createVoidhashClient } from "@voidhash/react-native";

export const voidhash = createVoidhashClient("vh_pk_...", {
  readOnly: true,
  scheme: "myapp",
});
ModeStarts purchasesSyncs transactions to VoidhashAcknowledges/finishes store transactions
readOnly: trueNoYesNo
readOnly: false (default)YesYesYes (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 the usePurchase hook.
  • client.setPersonAttributesSync(...) — it performs a network write, mirroring purchase. The fire-and-forget setPersonAttributes(...) 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:

utils/voidhash/client.ts
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:

utils/voidhash/client.ts
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.