Skip to main content
Present Encore retention offers to users at key moments in your app.

Overview

Encore allows you to present targeted offers to users in exchange for rewards (free trials, discounts, credits) at critical moments like cancellation flows or feature paywalls. The SDK provides multiple presentation methods to fit your app’s architecture. The SDK automatically manages entitlement state through isActivePublisher. Callbacks are optional - use them only when you need custom flow control (like proceeding with cancellation) or analytics tracking.

Third-Party Paywall Integrations

For Superwall and RevenueCat integrations, see the dedicated guides:

Native Integrations

The most concise and flexible way to present offers in SwiftUI and UIKit - perfect for button actions.
Button("Cancel Subscription") {
    Encore.placement("cancellation_flow")
        .onGranted { entitlement in
            print("User accepted: \(entitlement)")
        }
        .onNotGranted { reason in
            proceedWithCancellation()
        }
        .onLoadingStateChange { isLoading in
            self.showSpinner = isLoading
        }
        .show()
}
See the SDK Reference for complete API details: placement()
⚠️ Important: When presenting offers in critical flows (like cancellation), always ensure the user can proceed with their intended action. In the example above, proceedWithCancellation() is called in onNotGranted, ensuring users are never blocked from canceling their subscription.

SwiftUI: Async/Await

For when you need to await the result in Swift concurrency:
Button("Cancel Subscription") {
    Task {
        do {
            let result = try await Encore.placement("cancellation_flow").show()
            switch result {
            case .granted(let entitlement):
                print("User accepted: \(entitlement)")
            case .notGranted(let reason):
                proceedWithCancellation()
            }
        } catch {
            // Only system errors are thrown
            handleError(error as? EncoreError ?? .unknown)
        }
    }
}
See the SDK Reference for complete API details: placement()

SwiftUI: .encoreSheet() Modifier

The most declarative way to present offers using SwiftUI state:
import Encore
import SwiftUI

struct CancellationView: View {
    @State private var showEncoreOffer = false
    
    var body: some View {
        VStack {
            Text("We're sad to see you go...")
            Button("Cancel Subscription") { 
                showEncoreOffer = true 
            }
        }
        .encoreSheet(
            isPresented: $showEncoreOffer,
            onGranted: { entitlement in
                print("User accepted: \(entitlement)")
            },
            onNotGranted: { reason in
                print("Not granted: \(reason)")
                proceedWithCancellation()
            }
        )
    }
}
See the SDK Reference for complete API details: .encoreSheet()

Global Listeners for Analytics

Set up global placement listeners once during app initialization to automatically track all placement events across your app independent of context:
import Encore

@main
struct YourApp: App {
  init() {
    Encore.shared.configure(apiKey: "pk_your_api_key")
    
    // Track all placement events globally (chainable)
    Encore.placements
      .onGranted { placementId, entitlement in
        // Handle different placements with the placement ID
        switch placementId {
        case "cancellation_flow":
          Analytics.track("retention_success", properties: [
            "entitlement": "\(entitlement)"
          ])
        case "onboarding_gate":
          viewModel.paywallPurchaseWasSuccessful()
        default:
          Analytics.track("encore_offer_accepted", properties: [
            "placement_id": placementId,
            "entitlement": "\(entitlement)"
          ])
        }
      }
      .onNotGranted { placementId, reason in
        Analytics.track("encore_offer_declined", properties: [
          "placement_id": placementId,
          "reason": "\(reason)"
        ])
      }
  }
  
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

See the SDK Reference for complete API details: placement() - Global Listeners

Next Steps