Swift SDK reference
Client options, diagnostics, observer mode and methods of the Voidhash iOS SDK.
Everything VoidhashOptions accepts and the client exposes. To install the SDK and create the
client, see Swift SDK.
Client options
Pass a VoidhashOptions value to configure.
import Voidhash
import VoidhashUI
var options = VoidhashOptions()
options.paywallRenderer = VoidhashUIPaywallRenderer()
options.analyticsConsent = .pending
Voidhash.configure(publishableKey: "vh_pk_...", options: options)| Option | Default | Description |
|---|---|---|
baseUrl | https://api.voidhash.com | API origin, for self-hosted deployments. |
ingestUrl | baseUrl | Analytics origin only. |
debug | false | Logs diagnostics and marks requests as coming from a debug build. |
distinctId | Generated anonymous ID | Starts with a known user ID. See Start with a known ID. |
enabled | true | false makes the SDK inert. See Disabled clients. |
readOnly | false | Observer mode. See Observer mode. |
dev | false | Development mode. Honored only in debug builds. |
paywallRenderer | nil | VoidhashUIPaywallRenderer(), required to present paywalls. |
flowCapabilities | [.links] | What paywalls may do beyond purchases. See Choose what paywalls may do. |
preloadPlacements | [] | Location slugs to download on the very first launch. See Preload locations. |
featureFlags | See page | defaults, maxWaitMilliseconds (2000) and onUpdate. See Feature flags. |
screenTracking | Automatic | automatic, mapScreen and isAppScreenClass. See Track screens. |
automaticLifecycleEvents | true | Records $app_* and $sign_out events. See Automatic events. |
analyticsConsent | .granted | Consent at launch. See Analytics consent. |
installAttribution | true | Credits new installs to Apple Ads. See Credit installs to Apple Ads. |
onWarning | Unified log | Receives messages about failures that aren't thrown, such as a failed background refresh. |
onDiagnostic | nil | Receives the same situations as structured values. See Diagnostics. |
The SDK keeps its on-device state per publishable key and API origin. A different key or baseUrl
starts a new anonymous identity with empty queues, and nothing queued under the old one is sent.
Keep both stable across app updates.
Diagnostics
onDiagnostic receives a VoidhashDiagnostic each time the SDK handles a failure with a fallback
instead of throwing. Each value has a kind, a stable code (such as TRANSACTION_REJECTED), the
operation, whether it's retryable, an optional httpStatus and a message. Use it to forward
SDK health to your logging. The handler is called from any thread and must return quickly.
options.onDiagnostic = { diagnostic in
Logger.voidhash.notice("\(diagnostic.kind.rawValue) \(diagnostic.code): \(diagnostic.message)")
}kind | Meaning |
|---|---|
transport | A request failed or timed out and will be retried. |
eviction | A queue reached its cap and dropped its oldest entry. |
breaker | The SDK paused requests after repeated failures, or resumed them. |
auth | Voidhash rejected the publishable key and requests are paused, or the key works again. |
cache | A cache entry couldn't be read or written. |
consent | Analytics or attributes were skipped because consent isn't granted. |
Troubleshooting explains the codes you're most likely to see.
Observer mode
In observer mode, another billing SDK owns purchases. Set
options.readOnly = true: Voidhash then reports StoreKit transactions without finishing them, and
purchase(product:) throws READ_ONLY_PURCHASE_NOT_ALLOWED. Switch at runtime with
setReadOnly(_:) and read the mode from isReadOnly; a purchase in progress keeps the mode it
started with. Report purchases from another billing SDK
covers reportTransaction and syncPurchases.
Disabled clients
With enabled = false the SDK makes no requests, opens no StoreKit connection, starts no
observers and writes nothing to storage. getProducts() returns [], getCurrentPerson()
returns nil, presentFlow returns .failed(.disabled), and getDistinctId() returns an
anonymous ID that is never stored or sent.
Reconfigure and shut down
Calling configure again replaces the shared client. The previous client stops and hands its
queued events and transactions to the new one. A reference to the old client stays safe to call:
writes such as capture, identify and reportTransaction go to the new client when it uses the
same key and API origin, and reads return the values a disabled client returns.
shutdown() retires a client the same way without replacing it. It ends the StoreKit connection,
stops background work and writes the queues to disk.
Launches before the first unlock
iOS can launch an app in the background after a reboot, before the device is first unlocked. The
SDK can't read its stored state then, so it waits for the unlock instead of starting a new
identity. Until then, reads return empty, stale values without waiting, and events, identify
calls and reported transactions are kept in memory.
App extensions
An extension is a separate process. The SDK doesn't use App Groups, so an extension has its own
identity, queues and caches. To read the signed-in user's access in one, configure it with
options.distinctId set to the ID your app passes to identify, and set
automaticLifecycleEvents = false. Paywalls and store sheets aren't available in extensions.
Methods
| Method | Description | Guide |
|---|---|---|
waitForInitialization() | Waits for initialization; returns the project schema. | Create the client |
getProducts() | Products that resolved in the App Store, sorted by slug. | Purchases |
purchase(product:offerId:), purchase(productSlug:offerId:) | Buys a product; returns .completed or .deferred. | Purchases |
restorePurchases() | Restores the person's App Store purchases. | Purchases |
presentCodeRedemptionSheet(), showManageSubscriptions() | App Store sheets. | Purchases |
reportTransaction(…), syncPurchases() | Report purchases in observer mode. | Purchases |
pendingTransactionCount(), syncPendingWork() | Receipts waiting for Voidhash; send everything queued now. | Purchases |
getCurrentPerson(forceFetch:), getCurrentPersonState(forceFetch:) | The person and their perks; the state variant adds freshness. | Check access |
resetPersonCache() | Drops the cached person so the next read asks Voidhash. | Check access |
identify(externalUserId:email:name:), identifyState(…) | Links the device to your user ID. | Identify users |
setPersonAttributes(_:), setPersonAttributesState(_:) | Sets person attributes. | Identify users |
signOut(), reset() | Starts a new anonymous identity. | Identify users |
getDistinctId(), Voidhash.isAnonymousDistinctId(_:) | The current distinct ID, and whether it's anonymous. | Identify users |
presentFlow(placement:), presentFlow(placement:from:), dismissFlow() | Presents and closes paywalls. | Paywalls |
getPaywall(location:), getPaywallState(location:) | Resolves a location's paywall without presenting it, for custom presenters. | Paywalls |
getFeatureFlags(_:), getFeatureFlagsState(_:) | Evaluates feature flags. | Feature flags |
capture(_:properties:), screen(_:properties:), flush() | Records events and sends the queue. | Analytics |
trackPushOpened(userInfo:) | Records an opened push notification. | Analytics |
sessionId() | The current analytics session ID. | Analytics |
setAnalyticsConsent(_:), analyticsConsent | Sets and reads analytics consent. | Analytics |
setAdDataSharing(_:) | Records consent to share purchases with ad platforms. | Analytics |
setReadOnly(_:), isReadOnly | Switches observer mode at runtime. | Observer mode |
start(), shutdown() | Starts or retires a client you created yourself. | Reconfigure and shut down |
Methods for embedding hosts
These exist for hosts that embed the client as their transport, such as the React Native SDK's native layer. Don't call them from a Swift app.
| Method | What it does |
|---|---|
VoidhashClient.makeEmbedded(publishableKey:options:) | Creates a client that stores nothing and starts no observers; the host owns identity and caching. |
fetchSchema(distinctId:), fetchPerson(distinctId:), evaluateFeatureFlags(distinctId:keys:), resolvePaywallConfig(distinctId:locationSlug:) | Read the schema, person, flags or a location's paywall for an explicit distinct ID, without caching. |
identifyPerson(distinctId:externalUserId:email:name:), setPersonTraits(distinctId:traits:) | Identify a distinct ID or write person attributes, and return the updated person. setPersonAttributes is the app-facing form. |
syncStoreTransaction(…) | Records a store transaction the host observed in the durable outbox. |
injectInternalSchema(_:), recordDevelopmentPurchase(…) | Preview, testing and development-mode plumbing. |
Methods starting with host, queuePersonAttributes(_:), shutdownAfterFlush() | Behind @_spi(VoidhashHost): queue person attributes for the current identity without waiting, and flush (within the SDK's time budget) before retiring the client. |
Next steps
Something wrong or missing on this page? Tell us by email or on Discord.