Developer Docs / iOS SDK
GrowthCat iOS Referral Redemption SDK
GrowthCat is a lightweight Swift Package for referral-code redemption flows on top of RevenueCat. Use the built-inGrowthCatReferralViewfor the fastest path or build your own UI with the headless APIs.
Built For
In-app referral and promo-code redemption UX on iOS.
Includes
Validation, RevenueCat attribute syncing, Apple redemption handoff, and entitlement verification.
Not Included
Campaign management, referral creation, payouts, dashboards, or attribution reporting UI.
1. Overview
This SDK is focused on redemption-only flows. It validates referral and promo codes, sets the RevenueCat subscriber attribute$referrer_code, copies the validated code to pasteboard, opens Apple's native code redemption sheet, and verifies entitlement activation after the Apple flow returns.
Included
- Validate entered referral and promo codes.
- Set RevenueCat subscriber attribute `$referrer_code`.
- Open Apple native code redemption sheet.
- Verify entitlement activation after Apple flow.
Not included
- Creating referral codes or campaigns.
- Campaign lifecycle management or expiry controls.
- Admin dashboards, payout logic, or attribution reporting UI.
No backend integration is required. Install the SDK, configure it in your app, and GrowthCat handles the validation flow for you.
What happens during redemption
- GrowthCat validates the code.
- GrowthCat sets the RevenueCat attribute `$referrer_code`.
- The validated code is copied to pasteboard automatically.
- Apple's native redemption sheet opens automatically.
- The SDK verifies entitlement activation when your app becomes active again.
2. Requirements
- iOS 16+
- RevenueCat configured by your app before any GrowthCat call
- GrowthCat app SDK key (`gc_live_...`)
- GrowthCat SDK installed and initialized in your app
To get started, integrate the SDK into your app and complete the client-side configuration. No backend work is required.
3. Production readiness checklist
- RevenueCat SDK is configured before any GrowthCat calls.
- GrowthCat is initialized once at app startup.
- `verificationEntitlementID` matches your RevenueCat entitlement exactly.
- `.growthCatOnSuccess` is the only unlock point for premium access.
- `.growthCatOnError` is treated as pending and reconciled later.
- Referral entry point exists in a persistent surface such as settings, paywall, or onboarding.
4. Workspace selection
The SDK chooses the workspace itself and sends it in the X-GrowthCat-Workspace header on every request.
- By default,
DEBUGbuilds usesandbox. - Release builds use
live. - If you pass
environmentMode: .production, the SDK always useslive, even in aDEBUGbuild. - The header value is always either
sandboxorlive.
5. Installation
Add the package in Xcode via Swift Package Manager:
https://github.com/lapreamarcelo/GrowthCatSDK-ios.gitThen import the module where you need it:
import GrowthCat6. Initialize SDK
Initialize GrowthCat once, usually in `App.init()` after RevenueCat is configured.
public enum GrowthCatEnvironmentMode: Sendable {
case automatic
case production
}public static func initialize(
apiKey: String,
environmentMode: GrowthCatEnvironmentMode = .automatic,
logsEnabled: Bool = false
)There are two practical modes for initialization:
- Use
.automaticfor the default behavior. - Set
environmentMode: .productionto always use the live workspace.
Default behavior:
import SwiftUI
import GrowthCat
@main
struct DemoApp: App {
init() {
GrowthCat.initialize(
apiKey: "gc_live_123456",
environmentMode: .automatic,
logsEnabled: true
)
}
var body: some Scene {
WindowGroup { ContentView() }
}
}In a DEBUG build this resolves to sandbox. In a release build this resolves to live.
Force production from a debug build:
GrowthCat.initialize(
apiKey: "gc_live_123456",
environmentMode: .production,
logsEnabled: true
)There is no public force-sandbox override. That is intentional, because it is too easy to leave behind accidentally.
7. SDK config feature flags
GrowthCat stores the latest backend-provided SDK flags so your app can decide whether to show redeem UI or allow redeem actions. The SDK does not enforce these flags automatically.
if let sdkConfig = GrowthCat.shared.sdkConfig {
if sdkConfig.showCodeRedeemUI {
// Show your GrowthCat entry point.
}
if sdkConfig.allowCodeRedeemExecution {
// Let the user start the redeem flow.
}
}
if let readiness = GrowthCat.shared.sdkReadiness {
print("Can validate codes: \(readiness.canValidateCodes)")
}If you want to refresh them manually, for example after login or app foreground:
Task {
do {
let sdkConfig = try await GrowthCat.shared.refreshSDKConfig()
print("Latest SDK config: \(sdkConfig)")
} catch {
print("Failed to refresh SDK config: \(error)")
}
}Use these as optional feature flags
- `showCodeRedeemUI`: whether you want to present any redeem-code entry points.
- `allowCodeRedeemExecution`: whether you want to allow the user to actually start redemption.
- `sdkReadiness`: backend readiness status from the bootstrap call, useful for debug diagnostics.
8. Integration options
Recommended default
Start with the built-in view if you want RevenueCat-style integration semantics and the safest success/error callback model.
Option A: Built-in view + modifiers
Use GrowthCatReferralView and attach callback modifiers for success and pending/error handling.
import SwiftUI
import GrowthCat
struct SettingsView: View {
@State private var showReferral = false
var body: some View {
Button("Redeem Influencer Code") {
showReferral = true
}
.sheet(isPresented: $showReferral) {
GrowthCatReferralView(
theme: .default,
strings: .default,
verificationEntitlementID: "pro",
verificationTimeout: 20
)
.growthCatOnSuccess { result in
print("Access confirmed for code: \(result.normalizedCode)")
// Safe point to unlock premium features.
}
.growthCatOnError { error in
print("Redemption not confirmed: \(error)")
// Keep user in pending state and reconcile later.
}
}
}
}- User enters a code and taps apply.
- The SDK validates first, then copies the code and opens Apple's sheet.
- The SDK shows a confirming-access loader while entitlement verification runs.
- Invalid codes show a clear inline error and the Apple sheet does not open.
Callback guarantees
- `growthCatOnSuccess`: code validated, Apple sheet shown, app returned to active state, and `verificationEntitlementID` is active in RevenueCat.
- `growthCatOnError`: entitlement was not confirmed in this attempt. Common causes are user cancel, sync delay, network issues, or entitlement not active yet.
Treat `.growthCatOnSuccess` as the confirmed unlock point. Treat `.growthCatOnError` as pending and reconcile later on app active or next launch.
GrowthCatReferralView(
verificationEntitlementID: "pro",
verificationTimeout: 20
)
.growthCatOnSuccess { _ in
Task {
await api.markReferralRedeemed()
// unlockFeaturesForCurrentUser()
}
}
.growthCatOnError { error in
Task {
await api.markReferralPendingVerification(reason: error.localizedDescription)
}
}Option B: Fully custom UI with `GrowthCatRedeemer`
Use your own views and bind to `GrowthCatRedeemer` when you want UI control without dropping to raw APIs.
import SwiftUI
import GrowthCat
struct CustomReferralView: View {
@State private var code = ""
@StateObject private var redeemer = GrowthCatRedeemer()
var body: some View {
VStack(spacing: 12) {
TextField("Your code", text: $code)
.textFieldStyle(.roundedBorder)
Button(redeemer.isLoading ? "Applying..." : "Apply") {
Task { await redeemer.redeem(code) }
}
.disabled(redeemer.isLoading || code.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
if let error = redeemer.lastError {
Text("Error: \(error.localizedDescription)")
.font(.footnote)
}
if let result = redeemer.lastResult {
Text("Applied: \(result.normalizedCode)")
.font(.footnote)
}
}
.padding()
}
}A successful `redeem(...)` result here means the code validated and Apple's sheet was presented. It does not include the built-in view's post-Apple entitlement confirmation callbacks. Verify entitlement after the app returns before unlocking access.
Option C: Raw headless API
let result = try await GrowthCat.shared.redeemReferralCode("ABC123")
let isVerified = await GrowthCat.shared.verifyAppleRedemption(
entitlementID: "pro",
timeout: 20
)Use this path if you want full control over state handling, UI, and lifecycle coordination.
Verification recovery and retry
If the Apple sheet was shown but entitlement confirmation did not land yet, keep the user pending and retry on app active or next launch.
@MainActor
func reconcileReferralStatus() async {
let confirmed = await GrowthCat.shared.verifyAppleRedemption(
entitlementID: "pro",
timeout: 8
)
if confirmed {
await api.markReferralRedeemed()
// unlockFeaturesForCurrentUser()
} else {
await api.markReferralPendingVerification(reason: "Entitlement not active yet")
}
}@MainActor
func refreshReferralRedemptionStatus() async {
let status = await GrowthCat.shared.verifyAppleRedemption(
entitlementID: "pro",
timeout: 8
)
if status {
await api.markReferralRedeemed()
}
}8. Built-in view customization
If you pass no theme, GrowthCat uses an adaptive default style for light and dark mode. Customize colors, font, and shape with `GrowthCatTheme`, and customize copy with `GrowthCatStrings`.
let theme = GrowthCatTheme(
primaryColor: .orange,
backgroundColor: Color(.systemGroupedBackground),
textColor: .primary,
fontName: "AvenirNext-DemiBold",
cornerRadius: 14
)let strings = GrowthCatStrings(
title: "Have a Promo Code?",
description: "Enter your influencer code to unlock your offer.",
howItWorksTitle: "How it works",
howItWorksStepValidate: "1. We validate your code instantly.",
howItWorksStepCopy: "2. If valid, we copy it for you.",
howItWorksStepApple: "3. Apple redemption opens so you can paste and continue.",
placeholder: "Enter code",
buttonText: "Apply Discount",
successMessage: "Code validated. Opening Apple redemption...",
invalidCodeMessage: "That code does not exist. Please try again.",
networkErrorMessage: "Network error. Please try again."
)GrowthCatReferralView(theme: theme, strings: strings, verificationEntitlementID: "pro")10. Localization guide
GrowthCat does not impose a localization system. Pass already-localized text into `GrowthCatStrings`, typically using `String(localized:)` and your app's String Catalog keys.
let growthCatStrings = GrowthCatStrings(
title: String(localized: "growthcat.title"),
description: String(localized: "growthcat.description"),
howItWorksTitle: String(localized: "growthcat.how_it_works_title"),
howItWorksStepValidate: String(localized: "growthcat.how_it_works_validate"),
howItWorksStepCopy: String(localized: "growthcat.how_it_works_copy"),
howItWorksStepApple: String(localized: "growthcat.how_it_works_apple"),
placeholder: String(localized: "growthcat.placeholder"),
buttonText: String(localized: "growthcat.button_text"),
successMessage: String(localized: "growthcat.success_message"),
invalidCodeMessage: String(localized: "growthcat.invalid_code"),
networkErrorMessage: String(localized: "growthcat.network_error")
)
GrowthCatReferralView(strings: growthCatStrings, verificationEntitlementID: "pro")Recommended localization keys
- `growthcat.title`
- `growthcat.description`
- `growthcat.how_it_works_title`
- `growthcat.how_it_works_validate`
- `growthcat.how_it_works_copy`
- `growthcat.how_it_works_apple`
- `growthcat.placeholder`
- `growthcat.button_text`
- `growthcat.success_message`
- `growthcat.invalid_code`
- `growthcat.network_error`
- `growthcat.banner.title`
- `growthcat.banner.subtitle`
- `growthcat.banner.cta`
- Add the new language in your String Catalog or localized strings files.
- Translate your `growthcat.*` keys.
- Build `GrowthCatStrings` from localized keys.
- Pass those strings into `GrowthCatReferralView`.
11. Error handling
`redeemReferralCode(_:)` throws `GrowthCatError`.
- notInitialized
- invalidCode(message:)
- unauthorized(message:)
- rateLimited(retryAfter:)
- network(message:)
- server(statusCode:message:)
- revenueCatUnavailable(message:)
- unknown(message:)Best practices
- Initialize GrowthCat once at startup.
- Configure RevenueCat before redemption calls.
- Keep RevenueCat identity stable before starting the referral flow.
- Pass `verificationEntitlementID` to verify redemption outcome.
- Use `.growthCatOnSuccess` as confirmed redemption.
- Keep referral entry points visible in durable parts of the app.
12. Minimal API reference
public enum GrowthCat {
public static func initialize(
apiKey: String,
environmentMode: GrowthCatEnvironmentMode = .automatic,
logsEnabled: Bool = false
)
public static var shared: GrowthCatClient { get }
}
public protocol GrowthCatRedeeming {
var sdkConfig: SDKConfig? { get }
var sdkReadiness: SDKReadiness? { get }
func redeemReferralCode(_ code: String) async throws -> ReferralRedemptionResult
func validateReferralCode(_ code: String) async throws -> ReferralRedemptionResult
func refreshSDKConfig() async throws -> SDKConfig
func openAppleRedemptionSheet() async throws
func verifyAppleRedemption(entitlementID: String, timeout: TimeInterval) async -> Bool
}
@MainActor
public final class GrowthCatRedeemer: ObservableObject {
@Published public private(set) var isLoading: Bool
@Published public private(set) var lastResult: ReferralRedemptionResult?
@Published public private(set) var lastError: GrowthCatError?
public func redeem(_ code: String) async -> Result<ReferralRedemptionResult, GrowthCatError>
}