Swift SDK

Configure the Voidhash iOS SDK and look up its client options and methods.

The Swift SDK adds entitlements, feature flags, analytics, and observer-mode transaction reporting to native iOS apps. Use this page to create the client, set its options, and find the methods you need. Each feature also has its own guide, linked from the section that introduces it.

The initial release is observer-only

SDK-started purchases and hosted paywalls are temporarily unavailable. StoreKit transactions are still observed and submitted to Voidhash for revenue analytics, but the SDK never finishes them.

Compatibility

The SDK has three requirements.

  • iOS 15 or later. Paywalls require UIKit.
  • Swift 6, building with the Swift 5.9 language mode.
  • StoreKit 2.

The package ships two library products. Voidhash is the SDK you integrate against. VoidhashCore is the shared native core: the StoreKit engine, the paywall bridge and presenter, the API client, identity, caching, and the schema. The React Native SDK's native layer uses VoidhashCore as well.

You can also install the SDK with CocoaPods through the npm package @voidhash/ios. Declare both pods, because the local Voidhash pod cannot resolve its local VoidhashCore dependency on its own.

pod "VoidhashCore", :path => "../node_modules/@voidhash/ios"
pod "Voidhash", :path => "../node_modules/@voidhash/ios"

Create the client

Create one client and keep it for the lifetime of the app.

import Voidhash

let voidhash = Voidhash.configure(publishableKey: "vh_pk_...")

The publishable key is safe to include in the app. Never ship vh_sk_... secret keys.

configure returns the client. The same client is also reachable as Voidhash.shared. There is no schema argument, because the schema lives on the server and the SDK fetches it during initialization.

Initialization runs in the background. It connects to the store, resolves the schema, and reconciles transactions that were observed while the app was away. The first call that needs initialization waits for it implicitly. When you want to gate UI on it, await it explicitly.

try await voidhash.waitForInitialization()

If initialization fails, the SDK retries it on the next call that needs it.

Client options

Pass a VoidhashOptions value to configure to change the defaults.

var options = VoidhashOptions()
options.baseUrl = URL(string: "https://api.voidhash.com")! // API origin
options.ingestUrl = nil            // Analytics ingest origin; defaults to baseUrl
options.debug = false              // SDK logging + marks requests as from a debug build
options.distinctId = nil           // Seed the initial customer identity; usually omit
options.enabled = true             // false makes every call inert (no network at all)
options.readOnly = true            // Forced on in the initial observer-only release
options.onWarning = { message in } // Diagnostics never raised to the caller

let voidhash = Voidhash.configure(publishableKey: "vh_pk_...", options: options)

Each option is described below.

OptionDefaultDescription
baseUrlhttps://api.voidhash.comOverrides the API origin for self-hosted deployments.
ingestUrlSame origin as baseUrlOverrides only the analytics origin.
debugfalseEnables diagnostics and marks requests as debug-build traffic.
distinctIdGenerated anonymous IDSeeds the initial customer identity. Usually omit it and call identify().
enabledtrueSet it to false to ship the SDK fully inert.
readOnlytrueForced on while commerce features are unavailable.
onWarningUnified logReceives background failures that are not surfaced as thrown errors.

Disabled clients

With enabled: false, every method is inert. The SDK makes no requests and opens no store connection. getProducts() returns an empty list, and getCurrentPerson() returns nil.

Observer mode

The initial release always uses observer mode. Passing readOnly: false or calling setReadOnly(false) cannot transfer StoreKit ownership to Voidhash yet.

Products and transaction reporting

Load the product catalog and restore the customer's existing purchases.

let products = try await voidhash.getProducts()

if let product = products.first(where: { $0.slug == "pro-monthly" }) {
    // Prices are already formatted for the customer's storefront.
    print(product.displayPrice, product.interval ?? "one-time")
}

try await voidhash.restorePurchases()

Initialization and restore submit observed StoreKit transactions to Voidhash without finishing them. purchase(product:) remains in the SDK for a later commerce launch, but it currently throws READ_ONLY_PURCHASE_NOT_ALLOWED before it touches StoreKit. SDK-owned store sheets are inert.

After a successful host purchase, call reportTransaction(...) with its store transaction values. A valid report is persisted before it returns and delivered in the background, so a network or service outage delays reporting without failing the purchase flow. Use syncPurchases() for callbacks without transaction values and for restore recovery. Store observers and scans cannot recover a consumable already finished or consumed by the host. See Report and restore purchases for callback examples, error handling, and scan limits.

People and entitlements

Read the current person, identify them when they sign in, and reset the client when they sign out.

let person = try await voidhash.getCurrentPerson()

let isPro = person?.entitlements.grants.contains {
    $0.perkId == "pro" && $0.status == "active"
} ?? false

try await voidhash.identify(externalUserId: "user_123", email: "ada@example.com", name: "Ada")
try await voidhash.setPersonAttributes(["plan": .string("pro"), "seats": .number(3)])

let distinctId = await voidhash.getDistinctId()
await voidhash.reset() // Sign out: clears the identity and every cached response

The SDK caches the person snapshot for two days. After five minutes it treats the snapshot as stale but still serves it from the cache. Pass getCurrentPerson(forceFetch: true) to bypass the cache. The SDK refreshes the snapshot on its own after purchases, restores, and identity changes.

See Check access and Identify customers for the underlying concepts.

Feature flags

Evaluate the flags you need by key.

let flags = try await voidhash.getFeatureFlags(["new-onboarding"])
let enabled = flags.first { $0.key == "new-onboarding" }?.enabled == true

Pass nil to evaluate every flag. See Evaluate feature flags for variants and identity behavior.

Analytics

Capture an event, and call flush() when you need the queue sent right away.

await voidhash.capture("checkout_started", properties: ["plan": .string("pro")])
await voidhash.flush()

The SDK batches events, sending up to 20 per request and flushing every 5 seconds. Failed requests are retried with exponential backoff, so the wait grows after each failure. flush() sends everything queued right now. Event names beginning with $ are reserved for Voidhash events. See Capture analytics.

Paywalls

Hosted paywalls are temporarily unavailable. presentPaywall(...) returns .notAssigned, paywall resolution returns nil, and the SDK performs no network or presentation work.