Swift quickstart
Add Voidhash to a native iOS app and show your first paywall.
This guide takes an existing iOS app from installation to a paywall and access check. Requires iOS 15+ and a Swift 6 toolchain (the sources build with the Swift 5.9 language mode too).
Install the SDK
In Xcode, open File → Add Package Dependencies…, enter
https://github.com/voidhashcom/voidhash, and add the Voidhash library to your app target.
Or in a Package.swift:
dependencies: [
.package(url: "https://github.com/voidhashcom/voidhash", from: "0.0.1-alpha.1")
],
targets: [
.target(name: "App", dependencies: [.product(name: "Voidhash", package: "voidhash")])
]The package ships two products: Voidhash, the SDK you integrate against, and
VoidhashCore, the shared native core it depends on. You only touch VoidhashCore directly if
you build on top of the engine yourself.
Configure the client
Configure once at app start with your project's publishable key:
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 kicks off initialization in the background — store connection, schema fetch, and
reconciliation of transactions observed while the app was away. The first call that needs
initialization awaits it implicitly; use await voidhash.waitForInitialization() to wait
explicitly, for example on a loading screen.
Configure one test offer in Studio
Create the smallest complete catalog:
- Create a perk such as
premium. - Create a product, choose its billing duration, and attach the perk.
- Create a paywall that includes the product, then publish it.
- Create a paywall location such as
onboardingand assign the published paywall.
Connect App Store Connect before testing a release build — see Store setup. See Products and perks and Paywalls for the model behind these steps.
Present the paywall
Resolve the paywall by location and present it:
final class Paywalls: VoidhashPaywallDelegate {
func paywall(_ location: String, didPurchaseProductId productId: String, requestId: String?) {
unlockPremium()
}
func paywallDidDismiss(_ location: String) {}
}
let result = try await voidhash.presentPaywall(location: "onboarding", delegate: paywallsDelegate)
if result != .shown {
// `notAssigned` is the expected case when the location has no published
// paywall — fall back to your own screen instead of leaving the customer
// with nothing.
showFallbackUpgradeScreen()
}The SDK resolves the paywall assigned to the location, presents it in a full-screen WebView, and speaks the paywall bridge protocol natively: purchases and restores started inside the paywall run through the same purchase pipeline, close dismisses it, external links open in the browser, and custom events are captured into analytics.
Keep a strong reference to the delegate while the paywall is presented — it is held weakly.
Check access
Gate features on an active perk grant from the person snapshot:
let person = try await voidhash.getCurrentPerson()
let hasPremium = person?.entitlements.grants.contains {
$0.perkId == "premium" && $0.status == "active"
} ?? falseThe snapshot refreshes after a successful purchase or restore. See Check access for caching behavior and failure handling.
Run a test purchase
Build and run on a device or simulator signed into a Sandbox Apple account, then buy through the presented paywall. Purchases sync server-side, finish with StoreKit only after validation succeeds, and refresh the person snapshot.