Run in observer mode
Run Voidhash next to another billing SDK and move purchase ownership when you are ready.
Observer mode lets another billing SDK keep control of purchases while Voidhash reads the same store transactions and builds each person's purchases and entitlements from them. Use it to adopt Voidhash in an app that already sells in-app purchases, then hand ownership over without a gap.
The one invariant
Both SDKs can run in the same build. Exactly one of them may finish a transaction (StoreKit) or acknowledge it (Google Play). The other may read it, report it and show UI based on it.
In observer mode the Voidhash SDK never finishes, acknowledges or consumes a transaction, and the Voidhash servers never acknowledge or consume a purchase they did not start. Your existing SDK stays the only owner. Every step below keeps this true, including the release where ownership moves.
Google Play refunds a purchase that nobody acknowledges within three days. An ownership gap shows up as lost revenue, not as an error.
Turn on observer mode
Set readOnly when you configure the SDK:
import Voidhash
var options = VoidhashOptions()
options.readOnly = true
let voidhash = Voidhash.configure(publishableKey: "vh_pk_...", options: options)import com.voidhash.sdk.Voidhash
import com.voidhash.sdk.VoidhashOptions
val voidhash = Voidhash.configure(
context = this,
publishableKey = "vh_pk_...",
options = VoidhashOptions(readOnly = true),
)import { createVoidhashClient } from "@voidhash/react-native";
export const voidhash = createVoidhashClient("vh_pk_...", { readOnly: true });| Behavior | Observer mode | Owner mode (default) |
|---|---|---|
| Starts purchases | No | Yes |
| Observes store transactions | Yes | Yes |
| Sends transactions to Voidhash | Yes | Yes |
| Finishes or acknowledges transactions | No | Yes, after Voidhash accepts it |
| Reads products, people, perks, flags | Yes | Yes |
On launch and each time the app returns to the foreground, the SDK sends the purchases the store currently reports. In observer mode it also reports transactions the store delivers later, such as renewals and Ask to Buy approvals.
Blocked in observer mode
Starting a purchase fails with READ_ONLY_PURCHASE_NOT_ALLOWED before the store is touched. A
purchase button on a Voidhash paywall fails the same way. Restores, products, people, perks, feature flags, paywall lookups, analytics and
attribute updates keep working.
Report purchases your billing SDK makes
Call reportTransaction after each successful purchase your existing SDK makes, and for each
transaction its restore returns. Voidhash needs only the Apple transaction ID or the Google Play
purchase token; it verifies the rest with the store. Reporting never finishes or acknowledges the
transaction.
try await voidhash.reportTransaction(transactionId: transactionId)voidhash.reportTransaction(purchaseToken)await voidhash.client.reportTransaction({ platform: "ios", transactionId });
await voidhash.client.reportTransaction({ platform: "android", purchaseToken });Pass isRestore: true for a transaction from the user's own restore. When a callback exposes no
transaction, call syncPurchases() to send what the store currently reports. Each SDK's purchases
page covers the accepted inputs and errors: Swift,
Kotlin, React Native.
Switch ownership at runtime
Flip the mode on the running client instead of configuring a new one, which would drop the store connection, caches and analytics queue.
voidhash.setReadOnly(false) // Voidhash owns purchases
voidhash.setReadOnly(true) // back to observingvoidhash.setReadOnly(false) // Voidhash owns purchases
voidhash.setReadOnly(true) // back to observingvoidhash.client.setReadOnly(false); // Voidhash owns purchases
voidhash.client.setReadOnly(true); // back to observingisReadOnly returns the current mode. A purchase already in progress finishes under the mode it
started with, so switching mid-purchase never leaves a transaction unfinished.
Decide both halves of ownership from one flag: which SDK's purchase UI appears, and which SDK finishes transactions. Set the mode before you show purchase UI, not after a tap:
import { useEffect } from "react";
import { voidhash } from "../src/lib/voidhash";
export default function UpgradeEntry() {
const voidhashOwnsPurchases = useMyFeatureFlag("voidhash_purchases");
useEffect(() => {
voidhash.client.setReadOnly(!voidhashOwnsPurchases);
}, [voidhashOwnsPurchases]);
return voidhashOwnsPurchases ? <VoidhashUpgradeScreen /> : <LegacyUpgradeScreen />;
}When Voidhash has nothing to show for a location, presentFlow returns a failed result without
moving money, so showing your existing paywall instead is always safe. See
Paywalls in your app.
To ship the SDK completely inert, with no store connection or network, configure it with
enabled: false. Unlike readOnly, that is fixed when the client is created.
Migration sequence
Ship Voidhash in observer mode
Add the SDK with readOnly on and report your existing SDK's transactions. The purchase flow does
not change in this release.
Verify in the sandbox
On each platform, make a purchase, then renew, cancel, expire, refund and restore it. Each should appear on the person in People with the perks you expect. See Test purchases (Help center).
Compare with your current source of truth
For a sample of real subscribers, compare their perks in Voidhash with the access your current system grants. Resolve every mismatch before you continue.
Move purchase UI and ownership together
Behind one flag, as shown above, let Voidhash present the paywall and own purchases. Roll it out to a small share of users, then widen it. Never split UI and ownership across two releases.
Remove the previous SDK
Once the flag is at 100% and transactions reconcile, delete the old integration and the readOnly
option.
Retest purchases, renewals, cancellations and restores on both platforms after each step.
Next steps
- Migrate to Voidhash
- Test purchases (Help center)
- Check access from your backend with Node.js, PHP, Go, Rust or over HTTP
Something wrong or missing on this page? Tell us by email or on Discord.