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.
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:
| Property | Contents |
|---|---|
activePerkSlugs | Slugs of every perk the person can use now. |
entitlements.grants | Every entitlement, active or expired. See Entitlement fields. |
subscriptions.current | The current subscription, for account screens: its status (active, trialing or canceled) and expiresAt, or nil. See Statuses. |
subscriptions.history | Every subscription, with a status of active, trialing, canceled or expired. |
purchases.history | Past 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
| Field | Meaning |
|---|---|
perkSlug | The perk's slug. nil if the perk was deleted. |
perkId | The perk's internal ID. It isn't the slug. |
status | active or expired. |
source | What granted it: subscription or purchase. |
sourceId | The subscription or purchase that granted it. |
expiresAt | When 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.