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

Errors

Error types, codes and diagnostics of the Swift SDK, with what to do for each.

The Swift SDK throws errors with a stable code: StoreKit failures are wrapped in the SDK's own codes, and Voidhash API failures are mapped from the HTTP status.

Handle errors

Methods that can fail are async throws. Catch the error type, then branch on code. The error's string form is "CODE: message"; the message is for people, so never match on it.

import Voidhash
import VoidhashCore

func buy(_ product: VoidhashProduct) async {
    guard let voidhash = Voidhash.shared else { return }
    do {
        try await voidhash.purchase(product: product)
    } catch let error as VoidhashStoreError {
        switch error.code {
        case "USER_CANCELLED", "PURCHASE_PENDING":
            break // Expected outcomes, not failures.
        case "OFFER_UNAVAILABLE":
            showRegularPrice()
        default:
            showPurchaseError(error)
        }
    } catch {
        showPurchaseError(error)
    }
}

Most calls don't throw when Voidhash is unreachable: reads answer from the cache and writes are queued. Failures the SDK handles itself arrive as diagnostics instead.

Error types

TypeModuleThrown for
VoidhashStoreErrorVoidhashCoreStore operations, purchases and invalid arguments. Has code and message.
VoidhashApiErrorVoidhashCoreRequests Voidhash rejected or that never completed. Adds statusCode, tag and retryAfterMilliseconds.
ReconcileTransactionsErrorVoidhashA restore whose receipts Voidhash didn't accept. Its code is RECONCILE_TRANSACTIONS_FAILED; adds transactionIds.
FlowErrorVoidhashNot thrown: returned in .failed by presentFlow. Its description starts with a FLOW_ code.

Error codes

Configuration

CodeWhenWhat to do
STOREKIT_NOT_INITIALIZEDA store call ran on a disabled or shut-down client.Configure the client with enabled on, and use Voidhash.shared.
METHOD_NOT_AVAILABLE_PLATFORMThe method isn't available here, such as store sheets in development mode or on visionOS.Call it from a store build on iOS.
READ_ONLY_PURCHASE_NOT_ALLOWEDpurchase ran in observer mode.Buy through your other billing SDK.

AUTHENTICATION_FAILED, a rejected publishable key, is listed under Network and API.

Products and purchases

purchase(product:) and purchase(productSlug:) throw USER_CANCELLED and PURCHASE_PENDING as expected outcomes; handle them apart from failures.

CodeWhenWhat to do
USER_CANCELLEDThe user closed the App Store sheet or dismissed the App Store sign-in, during a purchase or a restore.Nothing.
PURCHASE_PENDINGThe purchase awaits approval, such as Ask to Buy.Nothing. The SDK syncs it when it clears, and access arrives then.
INVALID_PRODUCT_IDThe App Store didn't return the product, or no product has that slug.Check the product's App Store mapping.
OFFER_UNAVAILABLEThe offerId can't be applied, for example because the user isn't eligible, or Voidhash couldn't be reached to sign the offer. Nothing was bought.Offer the regular price.
PURCHASE_FAILEDStoreKit failed the purchase.Let the user try again.
PURCHASE_UNKNOWN_RESULTStoreKit returned a purchase result this SDK version doesn't know.Report the error; let the user try again.
TRANSACTION_VERIFICATION_FAILEDStoreKit couldn't verify the transaction, or Voidhash rejected it.Report the full error.
TRANSACTION_NOT_FOUNDVoidhash accepted the transaction, but the SDK no longer held it to finish with StoreKit.Nothing for access, which Voidhash recorded. Report it if it repeats.
WINDOW_SCENE_NOT_FOUNDNo active window scene to show the App Store sheet from.Call it while a scene is in the foreground.
PAYWALL_PRESENTER_NOT_AVAILABLEA development mode purchase ran on a platform without UIKit, such as macOS, so no confirmation sheet can show.Test development-mode purchases on iOS.
FAILED_TO_GET_PRODUCTSgetProducts() couldn't reach the App Store.Retry, or show the screen without prices.
FAILED_TO_RESTORE_PURCHASESrestorePurchases() couldn't reach the App Store.Ask the user to retry.
RECONCILE_TRANSACTIONS_FAILEDrestorePurchases() couldn't deliver a receipt, or Voidhash didn't accept it.Ask the user to retry later. The receipts stay queued.
INVALID_TRANSACTIONreportTransaction(transactionId:) got an empty ID.Pass the App Store transaction ID.
FAILED_TO_SHOW_MANAGE_SUBSCRIPTIONSshowManageSubscriptions() couldn't open the sheet.Link to the App Store's subscription settings instead.

Passing an unverified StoreKit result to reportTransaction(_:) rethrows StoreKit's verification error.

Paywalls and flows

presentFlow never throws. It returns .failed(FlowError); match on the case.

CodeWhenWhat to do
FLOW_NOT_ASSIGNEDNothing is assigned to the location, it doesn't exist or is archived, or the build needs a newer SDK.Show your fallback screen.
FLOW_UNAVAILABLEThe device never loaded this location and can't reach Voidhash, the download failed, or the paywall wasn't ready to show within 15 seconds.Show your fallback; retry later.
FLOW_RENDERER_MISSINGoptions.paywallRenderer isn't set.Add the renderer.
FLOW_UNSUPPORTED_PACKAGEThe paywall was built in a format this SDK can't read.Update the SDK.
FLOW_PRESENTATION_FAILEDThere was nothing to present from, the view controller you passed can't present, or the paywall can't be drawn.Present from a visible screen; report the error.
FLOW_DISABLEDThe client is disabled or shut down.Nothing.

A purchase started on a paywall reports its failures through onWarning and shows them on the paywall. A cancelled purchase isn't reported. See When nothing is shown.

Identity

CodeWhenWhat to do
INVALID_ARGUMENTidentify got a user ID that is blank, longer than 255 characters, contains NUL, or starts with vh_anon_ or vh:anon:. Or setPersonAttributes got an object or array value. Nothing changed.Fix the call. See Identify users.
IDENTITY_UNAVAILABLEA VoidhashApiError from a person or flag refresh on a client that can't use its saved identity: the device hasn't been unlocked since it started, or the client was shut down. Nothing was sent.Read again after the device is unlocked, from Voidhash.shared.

identify and setPersonAttributes don't throw for network failures or a rejected key: the SDK saves the write and sends it later. They throw a VoidhashApiError only when Voidhash refuses the write for good within the 2 seconds the call waits; identify then restores the previous identity.

Network and API

VoidhashApiError maps the HTTP status to a code. These are rarely thrown, since reads fall back to the cache and writes are queued; they also appear as the code of transport diagnostics.

CodeWhenWhat to do
AUTHENTICATION_FAILEDVoidhash itself answered 401 or 403: the publishable key was rejected. getCurrentPerson, getFeatureFlags and getPaywall throw it once, when nothing is cached.Check the key and its project. See API keys.
INVALID_REQUESTVoidhash answered 400, for example to an identify it refuses.Check the arguments.
NOT_FOUNDVoidhash answered 404.Check the ID you passed.
RATE_LIMIT_EXCEEDEDVoidhash answered 429.Wait retryAfterMilliseconds; don't retry in a loop.
API_ERRORAny other error status.Report the error with its statusCode and tag.
NETWORK_ERRORThe request never reached Voidhash.Retry later.
INVALID_RESPONSEVoidhash's answer couldn't be read.Report the error.

A 401 or 403 from something in front of Voidhash, such as a proxy, a firewall or an edge page, isn't a rejected key. The SDK treats it as a temporary failure: it retries, and nothing pauses.

Diagnostics

Diagnostics report failures the SDK handled itself, such as a delivery it will retry. They never throw. Receive them with the onDiagnostic option; onWarning gets the same situations as text. Diagnostic codes lists every code.

Next steps

  • Troubleshooting by symptom.
  • Make and restore purchases.
  • Error codes across platforms.

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

PreviousSwift SDK referenceNextDiagnostic codes

On this page

Handle errorsError typesError codesConfigurationProducts and purchasesPaywalls and flowsIdentityNetwork and APIDiagnosticsNext steps