Report and restore purchases
Report purchases from your existing billing integration to Voidhash.
The initial release runs in observer mode. Your existing billing integration starts purchases,
restores access, and finishes or acknowledges store transactions. Voidhash reports the purchases
it discovers without taking over those responsibilities. SDK-started purchases and hosted
paywalls are temporarily unavailable; purchase() returns an Err with
READ_ONLY_PURCHASE_NOT_ALLOWED before opening a store purchase flow.
Report a completed purchase or restore
Call reportTransaction(...) after each successful purchase, including purchases from another SDK's
paywall. Report each transaction returned by a restore callback as well. Configure Voidhash and
identify the purchasing user before starting the host purchase flow.
Only the store identifier is required: an Apple transaction ID or a Google Play purchase token. Voidhash supplies its configured bundle/package and captures the current SDK identity automatically. The backend fetches and verifies the purchase with the store; timestamps, quantities, product metadata, receipts, signatures and purchaser arguments are unnecessary.
// Apple: keep the transaction ID as a string, preserving every digit.
await voidhash.client.reportTransaction({ platform: "ios", transactionId });
// Google Play: the purchase token, not the order ID.
await voidhash.client.reportTransaction({ platform: "android", purchaseToken });Wait for useVoidhash().status === "ready" before calling the client. If the host exposes original
store data instead, the same method accepts a StoreKit JWS string or a Play bridge object with
originalJson. Voidhash extracts the identifier internally and discards the remaining payload;
no receipt-decoding dependency is needed in your application.
Legacy ReportedTransaction metadata such as productId, purchaseDate, quantity, receipt
and appAccountToken is optional and discarded. Android's optional purchaseState can mark a
report as pending or unspecified; those reports are ignored until reported as purchased.
The original Play JSON's pending state is converted internally. Multi-product bridge purchases
are rejected because the current purchase processing supports one product per purchase.
reportTransaction() returns Result<void, VoidhashError>. Missing or malformed required
identifiers return FAILED_TO_REPORT_TRANSACTION; an unavailable client returns
VOIDHASH_CLIENT_NOT_INITIALIZED. Network and delivery failures leave the report queued and
return Ok.
Reporting works after the host has finished or consumed a purchase because it does not scan the store. It never finishes, acknowledges or consumes transactions. Keep your billing integration's finalization and access checks in place.
A successful report is captured in the durable outbox before returning; delivery runs in the background. Success does not imply immediate backend acceptance. SDK diagnostics describe deferred delivery. Duplicate reports and observer callbacks use the same store identifier and retain the original captured identity through retries and app relaunches.
Recovery scans and restore callbacks
Use syncPurchases() for silent recovery when the host callback exposes no usable store
transaction values, including after its restore flow. It reports purchases the store still exposes,
skips receipts already accepted by Voidhash, and refreshes the current person. It does not prompt
for store authentication. Store-read failures reach the caller; delivery failures remain queued.
const result = await voidhash.client.syncPurchases();
if (result.isErr()) console.warn("Voidhash purchase scan failed", result.error);Initialization installs the observer and scans purchases in the background. Foreground and connectivity recovery also scan with a one-minute throttle shared across both triggers. In observer mode, scans can report unconsumed or unfinished consumables that remain visible without finalizing them. They cannot recover consumed or finished consumables, or arbitrary expired subscription history. iOS scans pending transactions and current entitlements; Android queries currently owned purchases.
syncPurchases() is a fallback, not a delivery guarantee. If the host hides transaction values and
finishes a consumable before Voidhash captures it, a later callback or scan cannot recover it. Keep
explicit reporting in the successful purchase callback whenever your host SDK exposes the values.
Explicit restoration
Connect your Restore Purchases button to restorePurchases(). It revalidates restorable purchases
for the identity that requested the restore, even when Voidhash already accepted those receipts.
The project's transfer policy controls whether ownership changes. A queued purchase retains its
original buyer and must be delivered before the restore can request another owner.
const result = await voidhash.client.restorePurchases();
if (result.isErr()) console.warn("Voidhash restore failed", result.error);On iOS, this calls AppStore.sync() and reads verified non-consumable transaction history,
including expired or revoked transactions. App Store authentication may appear, so call it only
from a user action. Android queries currently owned Play purchases; it cannot retrieve consumed
purchases or arbitrary expired subscription history.
Known consumables are excluded from explicit restoration. A store error or a receipt that cannot
be accepted fails the restore; deferred receipts stay queued for retry. An empty store can restore
successfully. Success does not imply an active entitlement: use the person's current grants to
decide whether to unlock access.
Reporting and silent sync after a host restore discover purchases. Use explicit restoration when Voidhash must revalidate cached receipts or apply its ownership policy for the current user.
Load products
useProducts() returns the store-backed products configured in your project. Prices come already
formatted for the customer's storefront, so you can render them directly.
const { data: products, error, isLoading } = voidhash.useProducts();
const monthly = products.get("monthly");data is a Map keyed by the project's product slugs. monthly is undefined when the store does not
return that product. That usually means the product is not available in the current sandbox
account, country, app version, or provider configuration.
Once you have a product, render its store-provided displayPrice.
if (!monthly) return null;
return <Text>{monthly.displayPrice}</Text>;Transactions missing from the dashboard
- Confirm successful purchase callbacks call
reportTransaction(...)with the original store values. UsesyncPurchases()for restore-only callbacks that expose no transaction values. - Verify that Voidhash uses the intended project key and purchasing user, and that the dashboard is showing the matching project, person, and store environment.
- Check SDK diagnostics for store-read or delivery failures. Analytics events arriving does not prove that a store transaction was discovered or submitted.
- For scan-only integrations, confirm the purchase is still exposed by the store. A finished or consumed consumable requires captured transaction values; a successful host purchase alone is insufficient for a later scan.