How do I use RevenueDot with the iOS SDK?
Set Purchases.proxyURL to your RevenueDot server before you call Purchases.configure, and set the entitlement verification mode to .disabled. The rest of your RevenueCat iOS SDK code stays the same. This covers iOS, iPadOS, macOS, tvOS, watchOS and visionOS apps on SDK 5.x.
Use the RevenueCat SDK you already ship (proxy mode)#
import RevenueCat
// Point the SDK at your RevenueDot server; nothing else in the app changes.
Purchases.proxyURL = URL(string: "https://revenuedot.example.com")!
Purchases.configure(
with: Configuration.Builder(withAPIKey: "appl_...")
// The default (.informational) logs every RevenueDot response as a failed signature check.
.with(entitlementVerificationMode: .disabled)
.build()
)- Set
proxyURLbeforeconfigure. It is a static property onPurchases. - Use the app's public key from RevenueDot (
appl_..., ormac_...for a Mac App Store app). If you ran the importer, your existing RevenueCat key keeps working. See Which key goes where. - For local testing, the simulator reaches your Mac at
http://localhost:8787.
After you migrate from RevenueCat, call syncPurchases() once on the first launch of the update. It sends the device's existing App Store purchases to RevenueDot, so current subscribers keep access even if their history was not imported.
// Once, after this update: send purchases made while the app talked to RevenueCat.
if !UserDefaults.standard.bool(forKey: "revenuedotSynced") {
_ = try? await Purchases.shared.syncPurchases()
UserDefaults.standard.set(true, forKey: "revenuedotSynced")
}Use the RevenueDot fork#
The fork is github.com/revenuedot/purchases-ios. It keeps the Swift modules RevenueCat and RevenueCatUI, so every import RevenueCat stays. It trusts RevenueDot's signing key, and its default host is https://api.revenuedot.app.
It is not published yet (2026-09-30). These are the planned install lines:
# Podfile (planned; the pods are not on CocoaPods trunk yet)
pod 'RevenueDotPurchases', '<version>'
pod 'RevenueDotPurchasesUI', '<version>' # only if you use RevenueCatUI// Package.swift (planned release tags look like 5.92.0-revenuedot)
.package(url: "https://github.com/revenuedot/purchases-ios", exact: "<version>-revenuedot")
// Products: "RevenueCat" and "RevenueCatUI"You can use it today from the patch branch. The branch is revenuedot/main-patches (upstream 5.92.0 in development plus the RevenueDot patches). A second branch, revenuedot/release-5.91.0, is the 5.91.0 release plus the patches.
// Swift Package Manager, from the branch
.package(url: "https://github.com/revenuedot/purchases-ios", branch: "revenuedot/main-patches")# CocoaPods, from the branch
pod 'RevenueDotPurchases', :git => 'https://github.com/revenuedot/purchases-ios.git', :branch => 'revenuedot/main-patches'The fork's default host is RevenueDot Cloud, so a Cloud project needs no Purchases.proxyURL. When you self-host, keep setting it to your server.
Trusted Entitlements#
- Stock SDK: it checks signatures with RevenueCat's key, so RevenueDot responses read as failed. The default mode,
.informational, logs the failure and still grants access. Set.disabledto stop the noise. Never use.enforcedwith the stock SDK: every request would fail. - Fork: it trusts RevenueDot Cloud's key. A self-hosted server signs with its own key (
REVENUEDOT_SIGNING_KEY), so keep.disabledor.informational, or build the fork with your own public key.
Details, key generation and self-host builds: Trusted Entitlements.
Check an entitlement and make a purchase#
The API is RevenueCat's, unchanged.
let customerInfo = try await Purchases.shared.customerInfo()
let isPro = customerInfo.entitlements["pro"]?.isActive == true
let offerings = try await Purchases.shared.offerings()
if let package = offerings.current?.availablePackages.first {
let result = try await Purchases.shared.purchase(package: package)
if !result.userCancelled, result.customerInfo.entitlements["pro"]?.isActive == true {
// Unlock pro features.
}
}The purchase goes to POST /v1/receipts. RevenueDot verifies it with Apple, which needs the App Store in-app purchase key on the app. See Connect the App Store.
Test Store#
Create a test_store app in RevenueDot and pass its test_... key to configure. The SDK then shows a Test Store alert instead of the App Store sheet.
- Test Store keys only work in Debug builds. In a Release build the SDK shows a "Wrong API Key" alert and stops the app on purpose.
- Known issue on older servers: the native iOS SDK could not load Test Store products from RevenueDot and reported "No base price found for product". The server sent
cycle_count: nullinGET /rcbilling/v1/subscribers/{id}/products. The server'smainbranch fixed this on 2026-09-30. If you see the error, update your server. See Known issues. - Test Store prices come from each product's Test Store price. Set it in the dashboard (Product catalog, Edit product) or with
test_store_priceonPOST /v2/projects/{project_id}/products; a product without one shows 0. - To test with Apple's sandbox or Xcode's StoreKit testing instead, see Sandbox testing.
More: Test Store.
Migrate from RevenueCat#
import RevenueCat
+// Point the SDK at your RevenueDot server; nothing else in the app changes. Set it before configure.
+Purchases.proxyURL = URL(string: "https://revenuedot.example.com")!
-Purchases.configure(withAPIKey: "appl_...")
+Purchases.configure(
+ with: Configuration.Builder(withAPIKey: "appl_...")
+ // The default (informational) logs every RevenueDot response as failed signature verification.
+ .with(entitlementVerificationMode: .disabled)
+ .build()
+)
+// Once, after this update: send purchases made while the app talked to RevenueCat.
+_ = try? await Purchases.shared.syncPurchases()The full order of steps is in Migrate from RevenueCat.
Examples#
- mobile/ios-swiftui: a SwiftUI paywall app. It builds for the iOS simulator; its README says exactly what was run.