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
| Type | Module | Thrown for |
|---|---|---|
VoidhashStoreError | VoidhashCore | Store operations, purchases and invalid arguments. Has code and message. |
VoidhashApiError | VoidhashCore | Requests Voidhash rejected or that never completed. Adds statusCode, tag and retryAfterMilliseconds. |
ReconcileTransactionsError | Voidhash | A restore whose receipts Voidhash didn't accept. Its code is RECONCILE_TRANSACTIONS_FAILED; adds transactionIds. |
FlowError | Voidhash | Not thrown: returned in .failed by presentFlow. Its description starts with a FLOW_ code. |
Error codes
Configuration
| Code | When | What to do |
|---|---|---|
STOREKIT_NOT_INITIALIZED | A store call ran on a disabled or shut-down client. | Configure the client with enabled on, and use Voidhash.shared. |
METHOD_NOT_AVAILABLE_PLATFORM | The 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_ALLOWED | purchase 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.
| Code | When | What to do |
|---|---|---|
USER_CANCELLED | The user closed the App Store sheet or dismissed the App Store sign-in, during a purchase or a restore. | Nothing. |
PURCHASE_PENDING | The purchase awaits approval, such as Ask to Buy. | Nothing. The SDK syncs it when it clears, and access arrives then. |
INVALID_PRODUCT_ID | The App Store didn't return the product, or no product has that slug. | Check the product's App Store mapping. |
OFFER_UNAVAILABLE | The 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_FAILED | StoreKit failed the purchase. | Let the user try again. |
PURCHASE_UNKNOWN_RESULT | StoreKit returned a purchase result this SDK version doesn't know. | Report the error; let the user try again. |
TRANSACTION_VERIFICATION_FAILED | StoreKit couldn't verify the transaction, or Voidhash rejected it. | Report the full error. |
TRANSACTION_NOT_FOUND | Voidhash 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_FOUND | No active window scene to show the App Store sheet from. | Call it while a scene is in the foreground. |
PAYWALL_PRESENTER_NOT_AVAILABLE | A 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_PRODUCTS | getProducts() couldn't reach the App Store. | Retry, or show the screen without prices. |
FAILED_TO_RESTORE_PURCHASES | restorePurchases() couldn't reach the App Store. | Ask the user to retry. |
RECONCILE_TRANSACTIONS_FAILED | restorePurchases() couldn't deliver a receipt, or Voidhash didn't accept it. | Ask the user to retry later. The receipts stay queued. |
INVALID_TRANSACTION | reportTransaction(transactionId:) got an empty ID. | Pass the App Store transaction ID. |
FAILED_TO_SHOW_MANAGE_SUBSCRIPTIONS | showManageSubscriptions() 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.
| Code | When | What to do |
|---|---|---|
FLOW_NOT_ASSIGNED | Nothing is assigned to the location, it doesn't exist or is archived, or the build needs a newer SDK. | Show your fallback screen. |
FLOW_UNAVAILABLE | The 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_MISSING | options.paywallRenderer isn't set. | Add the renderer. |
FLOW_UNSUPPORTED_PACKAGE | The paywall was built in a format this SDK can't read. | Update the SDK. |
FLOW_PRESENTATION_FAILED | There 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_DISABLED | The 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
| Code | When | What to do |
|---|---|---|
INVALID_ARGUMENT | identify 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_UNAVAILABLE | A 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.
| Code | When | What to do |
|---|---|---|
AUTHENTICATION_FAILED | Voidhash 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_REQUEST | Voidhash answered 400, for example to an identify it refuses. | Check the arguments. |
NOT_FOUND | Voidhash answered 404. | Check the ID you passed. |
RATE_LIMIT_EXCEEDED | Voidhash answered 429. | Wait retryAfterMilliseconds; don't retry in a loop. |
API_ERROR | Any other error status. | Report the error with its statusCode and tag. |
NETWORK_ERROR | The request never reached Voidhash. | Retry later. |
INVALID_RESPONSE | Voidhash'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
Something wrong or missing on this page? Tell us by email or on Discord.