RedChurn

Swift (iOS) SDK

Native SwiftUI SDK for iOS 15+. Configure once, render billing recovery, cancel saves, and win back, and edit all copy and offers from the RedChurn dashboard without an App Store release.

Install with AI

Click a tool to copy a ready-to-paste integration prompt with your SDK key and code snippets.

Download SDK v0.4.0
Or add via SwiftPM: https://github.com/redchurn/swift-sdk

Overview

The Swift SDK connects your iOS app to RedChurn through a single API key. It registers your RevenueCat App User ID, syncs remote configuration, and renders native SwiftUI for three retention scenarios.

Everything visible to the subscriber (titles, messages, buttons, offers) is edited in the dashboard. The SDK polls for changes and updates automatically — you do not ship a new build to change copy.

  • Cancel Save: intercept cancel taps with a native sheet
  • Billing Recovery: in-app dunning when RevenueCat reports a billing issue
  • Win back: reactivation prompt for churned subscribers
  • Remote config: flows, copy, and offers controlled from the dashboard

Requirements

  • iOS 15 or later (macOS 12+ also supported)
  • Swift 5.9 / Xcode 15 or later
  • SwiftUI (controllers are ObservableObject)
  • RevenueCat configured in your app with App User IDs
  • Push Notifications capability enabled if you use server push

1. Install (Swift Package Manager)

In Xcode: File → Add Package Dependencies, then paste the repository URL. Or add it to your Package.swift dependencies.

swift
// Package.swift
dependencies: [
  .package(url: "https://github.com/redchurn/swift-sdk", from: "0.4.0"),
],
// then add "RedChurn" to your target's dependencies

2. Configure at launch

Call Redchurn.configure once in your @main App init(). Get your SDK key from the RedChurn dashboard (Settings, or during onboarding). Pass the RevenueCat App User ID so RedChurn can match subscribers to webhook events. Keep notificationsEnabled to schedule local reminders from your dashboard copy.

swift
import SwiftUI
import Redchurn
import RevenueCat

@main
struct MyApp: App {
  init() {
    Redchurn.configure(
      sdkKey: "rdchrn_live_xxxxxxxx",
      rcAppUserId: Purchases.shared.appUserID,
      notificationsEnabled: true
      // debug: true            // optional — prints SDK diagnostics
    )
  }

  var body: some Scene {
    WindowGroup { RootView() }
  }
}

3. Set the App User ID after login

If the RevenueCat App User ID is not available at launch, set it after authentication so prompts target the right subscriber:

swift
// After your user logs in:
Redchurn.shared.setRCAppUserId(user.revenueCatAppUserId)

4. Cancel Save

Create a RedchurnCancelFlowController and mount CancelSaveSheet on your settings screen. interceptCancel() shows the save sheet (survey then offers) and returns false when nothing should be shown, so you fall back to the store cancellation UI. Apply the accepted offer in onOfferAccepted via RevenueCat — throw on failure so RedChurn only counts a save after a successful store/RC operation.

swift
import SwiftUI
import Redchurn
import RevenueCat

struct SubscriptionSettings: View {
  @StateObject private var cancelFlow = RedchurnCancelFlowController(
    rcAppUserId: Purchases.shared.appUserID,
    onOfferAccepted: { offer in
      try await applyRetentionOffer(offer) // throw on failure — save only on success
    }
  )

  var body: some View {
    VStack {
      Button("Cancel subscription") {
        Task {
          let intercepted = await cancelFlow.interceptCancel()
          if !intercepted {
            try? await AppStore.showManageSubscriptions(in: scene)
          }
        }
      }
      CancelSaveSheet(controller: cancelFlow)
    }
  }
}

5. Billing Recovery

When RevenueCat reports a billing issue, RedChurn shows an in-app dunning banner on the next open and (with notificationsEnabled) schedules local reminders. Mount BillingRecoveryBanner near the top of your home screen and re-check on notification taps.

swift
import SwiftUI
import Redchurn
import RevenueCat

struct HomeView: View {
  @StateObject private var billing = RedchurnBillingRecoveryController(
    rcAppUserId: Purchases.shared.appUserID
  )

  var body: some View {
    VStack {
      BillingRecoveryBanner(controller: billing)
      YourHomeContent()
    }
    .onReceive(NotificationCenter.default.publisher(for: .redchurnDidOpenFromNotification)) { note in
      guard let flow = note.object as? RedchurnFlow, flow == .billingRecovery else { return }
      Task { await billing.check() }
    }
  }
}

6. Win back

For churned subscribers (RevenueCat EXPIRATION), WinBackPrompt shows your reactivation offer on next open. Route the CTA to your paywall.

swift
import SwiftUI
import Redchurn
import RevenueCat

struct HomeView: View {
  @StateObject private var winBack = RedchurnWinBackController(
    rcAppUserId: Purchases.shared.appUserID
  )

  var body: some View {
    YourHomeContent()
      .background(
        WinBackPrompt(controller: winBack, onCtaPress: { openPaywall() })
      )
      .onReceive(NotificationCenter.default.publisher(for: .redchurnDidOpenFromNotification)) { note in
        guard let flow = note.object as? RedchurnFlow, flow == .winBack else { return }
        Task { await winBack.check() }
      }
  }
}

7. Route notification taps

So a tap on a billing recovery / win back notification opens the right flow, forward the payload to RedchurnNotificationRouting from your UNUserNotificationCenterDelegate. It posts redchurnDidOpenFromNotification (handled by the controllers above) and ignores unrelated notifications.

swift
import UserNotifications
import Redchurn

func userNotificationCenter(
  _ center: UNUserNotificationCenter,
  didReceive response: UNNotificationResponse,
  withCompletionHandler completionHandler: @escaping () -> Void
) {
  if RedchurnNotificationRouting.handleNotificationResponse(
    userInfo: response.notification.request.content.userInfo
  ) != nil {
    completionHandler()
    return
  }
  // your other notification handling…
  completionHandler()
}

8. Server push (optional)

Local notifications only fire while the app has run. To reach subscribers who never reopen the app, RedChurn also sends server push via APNs, triggered by the RevenueCat webhook. On iOS you must forward the native APNs device token — it is not auto-registered.

Request authorization, register for remote notifications, then forward the token in your AppDelegate. Add your APNs .p8 key (Key ID, Team ID, Bundle ID) once in Dashboard → Integrations → Push notifications.

  • Credentials are per app (BYOK): APNs .p8 key in Dashboard → Integrations
  • Sandbox vs Production: Debug builds from Xcode use sandbox; TestFlight and App Store use production
  • Test end-to-end from Dashboard → Events → Test push notifications
swift
import UIKit
import UserNotifications
import Redchurn

// 1. Ask permission and register with APNs (e.g. after login)
UNUserNotificationCenter.current()
  .requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
    guard granted else { return }
    DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() }
  }

// 2. Forward the APNs token to RedChurn
func application(
  _ application: UIApplication,
  didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
  Task { await Redchurn.shared.registerPushToken(deviceToken) }
}

Remote configuration

After the SDK is installed, all subscriber-facing content is managed in the dashboard. Toggle flows on or off, edit copy, change offers, and update styling. Changes propagate within 60 seconds.

The SDK polls a lightweight /version endpoint and only re-downloads configuration when something changed, plus a refresh when the app returns to foreground. Observe Redchurn.shared.ready / remoteConfig in SwiftUI if you need to react to it.

  • In-app flows and copy: Dashboard → Scenarios → In-app
  • Cancel offers: Dashboard → Cancel saves
  • Email sequences: Dashboard → Scenarios → Email
  • Branding and fallback CTA: Dashboard → Settings

How the SDK talks to RedChurn

All networking goes through an internal actor that talks to https://app.redchurn.io, authenticated with your SDK key as a Bearer token. Requests have a 12 second timeout and only /outcomes is retried, because it is the call that credits saved MRR.

rcAppUserId is the key that joins the SDK to your RevenueCat webhook events server-side, so a billing issue detected by webhook surfaces the right in-app prompt for the same subscriber.

Auth headers · bash
Authorization: Bearer rdchrn_live_xxxxxxxx
Accept: application/json
X-RedChurn-Sdk-Version: 0.4.0
Accept-Language: fr            # when a locale is set
Endpoints used by the SDK · text
POST /api/sdk/v1/register     heartbeat + APNs token registration
GET  /api/sdk/v1/config       full remote config (flows, copy, offers)
GET  /api/sdk/v1/version      lightweight change check (polled)
GET  /api/sdk/v1/prompts      contextual billing-recovery / win-back prompts
POST /api/sdk/v1/events       APP_OPEN, CANCEL_SESSION, PROMPT_SHOWN/DISMISSED, LINK_OPENED
POST /api/sdk/v1/outcomes     saved / declined / dismissed (retried)

Fail-safe behavior

The SDK never throws into your app. With an invalid SDK key it runs in no-op mode and your views render normally. interceptCancel() is fail-open so a subscriber can always reach the store.

Use the onError callback to log non-fatal issues to your monitoring tool.

swift
Redchurn.configure(
  sdkKey: "rdchrn_live_xxxxxxxx",
  rcAppUserId: Purchases.shared.appUserID,
  onError: { error in
    // Sentry.capture(message: "[RedChurn] \(error)")
  }
)

Production checklist

  • RevenueCat webhook connected and delivering events
  • SDK key from production environment (rdchrn_live_...)
  • App User ID set for every authenticated subscriber
  • Cancel button wired through interceptCancel(), not a direct store link
  • BillingRecoveryBanner and WinBackPrompt mounted on your home screen
  • Notification taps routed via RedchurnNotificationRouting
  • APNs key added in Dashboard → Integrations and token forwarded (if using server push)
  • In-app copy and offers reviewed in dashboard before launch

Install from SwiftPM or download

SwiftPM is the recommended path. You can also download the SDK archive from the button at the top of this page and drag the folder into your project, or mirror it in a private Git repo.

swift
.package(url: "https://github.com/redchurn/swift-sdk", from: "0.4.0")