VoidhashDocs
DevelopersHelp centerGuidesAPI reference
Dashboard
Get started
Introduction
Quickstart
How Voidhash works
Glossary
Versions and releases
Concepts
Teams, projects and environments
API keys
Products and perks
Offers
Subscription lifecycle
People and identity
Groups
Events
Features
Paywalls in your app
Feature flags and A/B tests
Paywall components
Push notifications
Ad attribution in your app
Client SDKs
React Native
Swift
Kotlin
Web
Your backend
Node.js
PHP
Go
Rust
Other languages
Payment sources
Forward store notifications
Developer tools
CLI
MCP server
Query analytics with VoidQL
Swift SDK
Swift quickstart
Display a paywall
Make and restore purchases
Check access
Identify users
Evaluate feature flags
Capture analytics
Groups
Track screens
Development mode
Swift SDK reference
Errors
Diagnostic codes
Troubleshooting

Check access

Gate features on the current person's active perks.

Gate features on the perks the current person holds. A person's entitlements say which perks they can use now and what granted each one. Perks and products are explained in Products and perks.

The SDK decides what to show on the device. It can't protect anything a user can reach by calling your API directly; check access on your server for that, with the server library for Node.js, PHP, Go or Rust, or over HTTP.

Gate a feature

hasActivePerk(_:) takes the perk slug you set in Studio and returns whether the person holds an active entitlement for it:

import Voidhash

func hasPremium() async -> Bool {
    let person = try? await Voidhash.shared?.getCurrentPerson()
    return person?.hasActivePerk("premium") ?? false
}

It doesn't compare expiry dates with the device clock. Voidhash decides when access ends, including grace periods and billing retry, and the next refresh reflects it. See Subscription lifecycle.

Gate on perks, not subscription status

Subscription status can't tell you which features a product unlocks, and it misses access from one-time purchases. Active perks are the source of truth.

Offline and failure behavior

A failed refresh isn't proof that the person has no access. While Voidhash is unreachable, getCurrentPerson() serves the last snapshot on the device. With no snapshot, it returns nil, which then means "unknown", not "no access". getCurrentPersonState() tells the two apart:

let state = try await voidhash.getCurrentPersonState()

if state.value == nil && state.isStale {
    // Unknown: the SDK couldn't ask Voidhash. Retry later; don't lock by default.
} else {
    let hasPremium = state.value?.hasActivePerk("premium") ?? false
}

The state carries value, isStale, isExpired (the snapshot is older than 2 days) and reason: fresh, refreshInFlight, refreshFailed or noCache. Branch on isExpired when you gate high-value content.

getCurrentPerson() throws AUTHENTICATION_FAILED once, and only when Voidhash rejects the publishable key and nothing is cached.

Read the person

The person snapshot holds more than perks:

PropertyContents
activePerkSlugsSlugs of every perk the person can use now.
entitlements.grantsEvery entitlement, active or expired. See Entitlement fields.
subscriptions.currentThe current subscription, for account screens: its status (active, trialing or canceled) and expiresAt, or nil. See Statuses.
subscriptions.historyEvery subscription, with a status of active, trialing, canceled or expired.
purchases.historyPast purchases.

getCurrentPerson() returns nil when Voidhash has no person for the current distinct ID yet, for example before the first purchase or identify. Treat that as no access.

Entitlement fields

FieldMeaning
perkSlugThe perk's slug. nil if the perk was deleted.
perkIdThe perk's internal ID. It isn't the slug.
statusactive or expired.
sourceWhat granted it: subscription or purchase.
sourceIdThe subscription or purchase that granted it.
expiresAtWhen access ends, or nil for access without an end.

Refresh behavior

The SDK caches the person for 2 days and serves it at once. After 5 minutes the snapshot is stale: a read still returns it, but waits up to 500 milliseconds for a refresh first. The SDK refreshes the person after purchases, restores and identity changes; restorePurchases() and syncPurchases() wait for that refresh before they return.

To ask Voidhash now, pass forceFetch: true. It waits up to the 10-second request timeout and returns the cached snapshot, marked stale, if Voidhash doesn't answer.

let person = try await voidhash.getCurrentPerson(forceFetch: true)

resetPersonCache() drops the cached snapshot, so the next read asks Voidhash. Use it when your backend changed the person, for example after a server-side purchase on the web.

Environments

The publishable key the client uses decides which purchases unlock perks. With the production key (vh_pk_…), production purchases count, plus store sandbox purchases such as TestFlight and App Review while they unlock production apps. With the test key (vh_pk_test_…), store sandbox, simulated and test mode purchases count, never production ones. Use the test key in debug builds and the production key in release builds.

Next steps

  • Identify users so access follows them across devices.
  • Check access from your backend with Node.js, PHP, Go, Rust or over HTTP
  • Troubleshooting

Something wrong or missing on this page? Tell us by email or on Discord.

PreviousMake and restore purchasesNextIdentify users

On this page

Gate a featureOffline and failure behaviorRead the personEntitlement fieldsRefresh behaviorEnvironmentsNext steps