Skip to main content
ZeroSettle works alongside RevenueCat. You can use RevenueCat for StoreKit subscription management while routing eligible purchases through ZeroSettle’s web checkout for lower fees. There are two integration approaches: Use the ZeroSettleIAP SDK alongside RevenueCat in your app. This gives you full control over the purchase flow.

Configuration

When using RevenueCat, disable ZeroSettle’s StoreKit listener to avoid conflicts:
import ZeroSettleIAP
import RevenueCat

@main
struct YourApp: App {
    init() {
        // Configure RevenueCat first
        Purchases.configure(withAPIKey: "your_revenuecat_key")

        // Configure ZeroSettle with StoreKit sync disabled
        ZeroSettleIAP.shared.configure(.init(
            publishableKey: "your_zerosettle_key",
            syncStoreKitTransactions: false  // RevenueCat manages StoreKit
        ))
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
                .zeroSettleIAPHandler()
        }
    }
}
Always set syncStoreKitTransactions: false when using RevenueCat. Both SDKs listening for StoreKit transactions will cause conflicts.

User Identity

Pass RevenueCat’s appUserID as the userId when making ZeroSettle purchases. This links web checkout purchases to the correct RevenueCat customer:
let rcUserId = Purchases.shared.appUserID

// Fetch ZeroSettle products
let products = try await ZeroSettleIAP.shared.fetchProducts(userId: rcUserId)

// Purchase via web checkout
try await ZeroSettleIAP.shared.purchase(
    productId: product.id,
    userId: rcUserId
)

Hybrid Paywall

Show both ZeroSettle (web checkout) and RevenueCat (StoreKit) purchase options:
struct PaywallView: View {
    @State private var showPaymentSheet = false
    let zsProduct: ZSProduct      // From ZeroSettleIAP
    let rcPackage: Package        // From RevenueCat

    var body: some View {
        VStack(spacing: 16) {
            // Web checkout — lower fees
            Button("Subscribe — \(zsProduct.webPrice.formatted)") {
                showPaymentSheet = true
            }
            .zsPaymentSheet(
                isPresented: $showPaymentSheet,
                product: zsProduct,
                userId: Purchases.shared.appUserID
            ) { result in
                if case .success = result {
                    // Refresh RC customer info to pick up the webhook
                    try? await Purchases.shared.customerInfo()
                }
            }

            // StoreKit fallback via RevenueCat
            Button("Subscribe via App Store — \(rcPackage.localizedPriceString)") {
                Task {
                    try? await Purchases.shared.purchase(package: rcPackage)
                }
            }
            .foregroundStyle(.secondary)
        }
    }
}

Webhook Sync

When a user purchases via ZeroSettle web checkout, you need to sync the entitlement to RevenueCat. Configure a webhook in your ZeroSettle dashboard that posts to RevenueCat’s REST API:
  1. Go to your ZeroSettle dashboard
  2. Add a webhook endpoint pointing to your backend
  3. Your backend receives the purchase event and calls RevenueCat’s Grant Entitlement API
This ensures RevenueCat’s CustomerInfo stays in sync with web checkout purchases.

Option 2: RevenueCat Paywall Custom Button

If you use RevenueCat’s paywall builder, you can add a custom checkout button that links to ZeroSettle’s hosted checkout:
  1. In your RevenueCat paywall template, add a purchase button with custom checkout
  2. Set the URL to your ZeroSettle checkout link
  3. Configure a URL scheme in your app to handle the callback

Handle the Callback

@main
struct YourApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    if url.scheme == "yourapp" {
                        let status = url.lastPathComponent
                        switch status {
                        case "success":
                            // Refresh RevenueCat customer info
                            Task {
                                try? await Purchases.shared.customerInfo()
                            }
                        case "cancelled":
                            break
                        default:
                            break
                        }
                    }
                }
        }
    }
}
The SDK integration (Option 1) provides a better user experience since the payment sheet stays inside your app. The custom button approach is useful if you want to use RevenueCat’s paywall builder without writing custom UI code.