Retention Messaging lets developers configure value proposition messages and promotional offers that appear to subscribers on the App Store cancellation page, reducing churn at the critical moment a customer considers canceling. It supports both a static App Store Connect configuration and a real-time server-to-server API for per-customer decisioning.
• Subscriptions using Retention Messaging see an average +1.4 point save rate increase; promotional offer messages achieve up to +5.5 points (+223%), directly protecting subscription revenue.
• Real-time Retention Messaging allows server-side decisioning per customer, enabling personalized offers, switch-plan alternatives, or targeted messaging without an app update.
• A new offerType value of 5 in signed transactions and renewal info identifies redeemed retention offers, letting your backend accurately track retention campaign performance.
Demonstrates how to detect a redeemed retention offer (offerType 5) in a StoreKit 2 transaction and display a confirmation to the user, covering the core new API surface developers must handle on the client side.
import SwiftUI
import StoreKit
// Represents a decoded retention offer redemption from a StoreKit transaction
struct RetentionOfferInfo: Identifiable {
let id: UUID = UUID()
let productID: String
let offerID: String?
let discountType: Transaction.OfferType?
let offerPeriod: String
}
@MainActor
final class RetentionOfferViewModel: ObservableObject {
@Published var retentionRedemptions: [RetentionOfferInfo] = []
@Published var isLoading = false
// offerType == 5 indicates a retention offer was redeemed (new in iOS 27)
private let retentionOfferTypeValue: Int = 5
func checkForRetentionOffers() async {
isLoading = true
defer { isLoading = false }
var found: [RetentionOfferInfo] = []
for await result in Transaction.currentEntitlements {
guard case .verified(let transaction) = result else { continue }
// Check for the new retention offer type (rawValue 5)
if let offerType = transaction.offerType,
offerType.rawValue == retentionOfferTypeValue {
let info = RetentionOfferInfo(
productID: transaction.productID,
offerID: transaction.offerID,
discountType: transaction.offerType,
offerPeriod: transaction.subscriptionGroupID ?? "unknown"
)
found.append(info)
}
}
retentionRedemptions = found
}
}
struct RetentionOfferInspectorView: View {
@StateObject private var viewModel = RetentionOfferViewModel()
var body: some View {
NavigationStack {
Group {
if viewModel.isLoading {
ProgressView("Checking transactions…")
} else if viewModel.retentionRedemptions.isEmpty {
ContentUnavailableView(
"No Retention Offers Found",
systemImage: "tag.slash",
description: Text("No subscriptions with a redeemed retention offer (offerType 5) were found.")
)
} else {
List(viewModel.retentionRedemptions) { info in
VStack(alignment: .leading, spacing: 6) {
Text(info.productID)
.font(.headline)
if let offerID = info.offerID {
Label("Offer ID: \(offerID)", systemImage: "tag")
.font(.subheadline)
.foregroundStyle(.secondary)
}
Label("Subscription Group: \(info.offerPeriod)", systemImage: "person.2")
.font(.subheadline)
.foregroundStyle(.secondary)
Text("Retention Offer Redeemed âś“")
.font(.caption)
.foregroundStyle(.green)
.bold()
}
.padding(.vertical, 4)
}
}
}
.navigationTitle("Retention Offers")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Refresh") {
Task { await viewModel.checkForRetentionOffers() }
}
}
}
.task {
await viewModel.checkForRetentionOffers()
}
}
}
}
#Preview {
RetentionOfferInspectorView()
}Real-time Retention Messaging requires a fast server — if your endpoint doesn't respond in time, the App Store falls back first to your App Store Connect retention message, then to a default API-configured message. The new offerType 5 for retention offers is only surfaced in StoreKit signed transaction/renewal info after the customer redeems an offer. Images configured alongside an offer are replaced by the offer UI automatically. Monthly subscriptions with 12-month commitment (introduced in iOS 26.5) require the billingPlanType field in your alternateProduct real-time response.
Requires an active auto-renewable subscription in App Store Connect. Real-time Retention Messaging requires passing a performance test in sandbox before production use. Static Retention Messaging via App Store Connect has no special hardware requirements.
More iOS 27 APIs land every week.
Get notified when new capabilities are published — no noise, just signal.