The Problem This Solves
You've launched a subscription app, and your conversion funnel has a gap you can't ignore: free-to-paid conversion is lower than you'd like, churned users aren't coming back, and you have no systematic way to retain users who are about to cancel. Apple gives you four distinct offer types to address each stage of the subscription lifecycle — but the devil is in the implementation details.
Each offer type has different eligibility rules, requires different amounts of setup, and interacts with the RevenueCat SDK in a completely different way. Getting them wrong means showing full pricing to users who should see a trial, or silently failing to apply a discount because a signing key wasn't configured. This guide walks through every offer type from setup to analytics so you ship it correctly the first time.
The Four Offer Types at a Glance
Before writing a single line of code, understand which tool fits which job. Source
| Offer Type | Target Audience | Subscription Key Required | Applied Automatically? |
|---|---|---|---|
| Introductory Offer | New users | Required in StoreKit 2 | ✅ Yes (by Apple) |
| Promotional Offer | Existing & lapsed users | ✅ Required | ❌ No — you trigger it |
| Offer Code | New & existing users | ✅ Required | ❌ No — user redeems it |
| Win-Back Offer | Lapsed users (iOS 18+) | ✅ Required | Via StoreKit message |
⚠️ In-App Purchase Promo Codes (Apple's older code system) are not recommended by RevenueCat. They don't auto-renew, revenue won't be accurate in Charts, and new promo codes cannot be created after March 26, 2026. Source
Step 0: Upload Your In-App Purchase Key (Required for Almost Everything)
All offer types except basic introductory offers on StoreKit 1 require RevenueCat to cryptographically sign requests with Apple. This means you must upload an In-App Purchase Key before anything else.
Critical: If you're using Purchases SDK v5+ (StoreKit 2), transactions will fail to be recorded without this key. Source
How to generate and upload the key
- In App Store Connect, go to Users and Access → Integrations → In-App Purchase.
- Click Generate In-App Purchase Key and download the
.p8file — you get one shot at downloading it. Source - In the RevenueCat dashboard, navigate to your App Store app under Apps & Providers → In-app purchase key configuration.
- Upload the
.p8file, then paste in the Issuer ID from the same page in App Store Connect.
💡 Tip: If you don't see an Issuer ID in App Store Connect, create any App Store Connect API key first — the Issuer ID will then appear at the top of the page and is shared across key types. Source
Once uploaded, RevenueCat will validate the key and show a "Valid credentials" confirmation. If validation fails, confirm the key is still Active (not Revoked) in App Store Connect and that your Bundle ID has the correct capitalisation in the RevenueCat dashboard. Source
Part 1: Introductory Offers
How they work
Introductory offers (including free trials) are automatically applied by Apple when an eligible user purchases a product. You don't call any special SDK method to trigger one — Apple's payment sheet handles it. Source
Eligibility rules for iOS: - New subscribers are always eligible. - Lapsed subscribers who renew are eligible if they haven't previously used an introductory offer for any product in the same subscription group. - Existing subscribers are not eligible, including those upgrading, downgrading, or cross-grading within a group. Source
Setting up the offer in App Store Connect
Navigate to your subscription product in App Store Connect, open the Introductory Offers tab, and click the + icon. You'll configure the start/end date, territory, and the offer type (free trial, pay-as-you-go, or pay-up-front). Source
Allow up to 24 hours after creating an offer for it to propagate before testing. Source
Checking eligibility to show the right paywall copy
Even though Apple applies the discount automatically, you need to show the correct pricing copy to users. Use RevenueCat's eligibility check to control what they see:
// Check eligibility before rendering paywall copy
let products = await Purchases.shared.offerings()?.current?.availablePackages
.map { $0.storeProduct.productIdentifier } ?? []
let eligibility = await Purchases.shared.checkTrialOrIntroductoryPriceEligibility(
productIdentifiers: products
)
for (productId, status) in eligibility {
switch status.status {
case .eligible:
// Show "Start your 7-day free trial" copy
print("\(productId): eligible for intro offer")
case .ineligible:
// Show standard pricing copy
print("\(productId): NOT eligible for intro offer")
case .unknown:
// Fallback — Apple's payment sheet will show the correct terms
print("\(productId): eligibility unknown")
@unknown default:
break
}
}
⚠️ iOS only. Calling
checkTrialOrIntroductoryEligibilityon cross-platform SDKs (React Native, Flutter, etc.) will not return a valid result on Android. Source
RevenueCat uses a best-effort approach based on the customer's previous purchase history. The native payment sheet will always display the correct eligibility before the user confirms. Source
Part 2: Promotional Offers
Promotional Offers, introduced in iOS 12.2, let you present a discounted price directly inside your app to existing or lapsed subscribers. Unlike introductory offers, nothing is applied automatically — you fetch a signed discount object and pass it to the purchase call.
1. Create the offer in App Store Connect
In App Store Connect, open your subscription product and click the + next to Subscription Prices, then choose Create Promotional Offer. You'll configure two key fields:
- Reference Name — internal label only
- Promotional Offer Product Code — the identifier your app code will use (e.g.,
monthly_winback_3mo_free)
You then choose the offer type: Pay-up-front, Pay-as-you-go, or Free (analogous to a free trial). Source
2. Fetch the signed PromotionalOffer object
RevenueCat handles signing the offer request with your In-App Purchase Key. Call getPromotionalOffer with the product and the discount you want to apply:
// 1. Get the product
guard let product = await Purchases.shared.offerings()?.current?
.monthly?.storeProduct else { return }
// 2. Find the specific discount by its offer identifier
guard let discount = product.discounts.first(where: {
$0.offerIdentifier == "monthly_winback_3mo_free"
}) else { return }
// 3. Fetch the signed promotional offer from RevenueCat
do {
let promoOffer = try await Purchases.shared.promotionalOffer(
forProductDiscount: discount,
product: product
)
// 4. Purchase with the promotional offer applied
let result = try await Purchases.shared.purchase(
product: product,
promotionalOffer: promoOffer
)
print("Purchase succeeded: \(result.customerInfo.activeSubscriptions)")
} catch {
print("Error: \(error.localizedDescription)")
}
If the user is ineligible, RevenueCat will return an
INELIGIBLE_ERROR. UsegetPromotionalOffer— not a manual eligibility check — as the definitive gate before attempting to apply a promotional offer. Source
3. Display via Customer Center (zero-code retention)
If you've integrated RevenueCat's Customer Center, you can skip manual implementation for retention flows entirely. Promotional offers can be assigned to cancellation survey responses and refund request paths directly in the RevenueCat dashboard. When a user is about to cancel and selects a survey response you've paired with an offer, the SDK automatically presents it. Source
Part 3: Win-Back Offers
Introduced with iOS 18, win-back offers let you define custom pricing for users whose subscriptions have already expired. Apple determines eligibility based on criteria you configure. Because win-back offers run entirely over StoreKit 2, they have hard requirements: Source
- iOS 18.0+ only
- RevenueCat iOS SDK version 5+
- In-App Purchase Key must be uploaded to RevenueCat
1. Configure the win-back offer in App Store Connect
In App Store Connect, open your subscription product (which must already be approved by App Review) and click the + next to Subscription Prices. You'll see Create Win-Back Offer if the product is eligible. Configure the offer's pricing and eligibility criteria (e.g., only users who were subscribers for at least N months). Source
If you don't see the "Create Win-Back Offer" option, confirm the subscription product has cleared App Review.
2. Handle the StoreKit message in your app
When a lapsed user opens your app on iOS 18+, StoreKit proactively sends your app a message indicating the user is eligible for a win-back offer. Handle this via the RevenueCat SDK's message listener:
import RevenueCat
// In your app's scene or root view setup — requires iOS SDK v5+
Purchases.shared.delegate = self
// MARK: - PurchasesDelegate
extension YourDelegate: PurchasesDelegate {
func purchases(
_ purchases: Purchases,
readyForPromotedProduct product: StoreProduct,
purchase startPurchase: @escaping StartPurchaseBlock
) {
// Called when StoreKit delivers a win-back or promoted IAP message
startPurchase { transaction, info, error, cancelled in
if let info = info, error == nil {
print("Win-back purchase complete: \(info.activeSubscriptions)")
}
}
}
}
3. Redeem in a StoreKit StoreView (SwiftUI)
For a purely SwiftUI approach, you can embed a StoreView that automatically surfaces eligible win-back offers. Both the StoreKit message and StoreView redemption paths require iOS SDK version 5+. Source
Priority behaviour
If a subscriber is eligible for multiple offers on the same product, win-back offers set to high priority in App Store Connect will be displayed instead of normal priority ones in StoreKit views. Plan your offer priority carefully if you run concurrent promotions. Source
Part 4: Offer Codes
Offer Codes (iOS 14+) are alphanumeric codes you generate in App Store Connect and distribute via email, social media, or in-app prompts. They support both new and existing users, auto-renew after the offer period ends, and work with presentCodeRedemptionSheet. Source
Requirements: iOS SDK 3.8.0+, In-App Purchase Key uploaded.
Present the redemption sheet
// iOS 14+: present Apple's native offer code redemption sheet
do {
try await Purchases.shared.presentCodeRedemptionSheet()
} catch {
print("Could not present redemption sheet: \(error)")
}
When the user taps a link to redeem an offer code outside your app, iOS takes them to the App Store to complete the redemption. To handle this gracefully in-app, use the presentCodeRedemptionSheet approach above so users never leave your flow. Source
Add offer code support to a RevenueCat Paywall
If you use RevenueCat Paywalls, you can add a button component configured to navigate to the offer code redemption sheet. Customers who tap it will see Apple's native redemption UI without you writing any extra code. Supported SDK versions are listed in the paywall offer docs. Source
Part 5: Tracking Offer Performance in RevenueCat Charts
Shipping offers without measuring them is a waste. RevenueCat's Charts let you filter and segment every key metric by offer type and specific offer identifier. Source
Filter and segment by offer type
In any chart (Revenue, New Paid Subscriptions, Trial Conversion, Subscription Retention, etc.), add:
- Segment by
Offer Typeto break down totals into: Introductory Offer, Promotional Offer, Offer Code, Win-Back Offer, or no offer. - Filter by
Offer Type = offer_codethen Segment byOfferto see performance broken down by individual offer identifier — useful for A/B comparing two promo codes.
The offer type values recognized by RevenueCat Charts include: Source
| Offer Type Label | What it represents |
|---|---|
| Introductory Offer | Trial or discounted intro period on first purchase |
| Promotional Offer | Manually triggered discount for existing/lapsed users |
| Offer Code | User-redeemed alphanumeric code |
| Win-Back Offer | Offer presented to a user with an expired subscription |
For win-back offers specifically, RevenueCat's Charts recognize offer identifiers like winback_monthly_offer with the rc prefix convention, so name your App Store Connect offers with readable identifiers from day one. Source
Customer Lists: find users by offer type
In Customers → Customer Lists, you can filter your entire customer base by Latest Offer Type to build cohorts — for example, all users who converted via an offer code — and export them for follow-up campaigns. Source
Scheduled Data Exports
RevenueCat's data exports include offer and offer_type columns per transaction, and an is_in_intro_offer_period boolean, so you can build your own attribution models in your data warehouse. Source
Sandbox Testing Note
For sandbox and production testing of subscription offers, offers are signed using the In-App Purchase Key you uploaded to RevenueCat. However, if you're using a StoreKit Configuration file in Xcode, the signing key must be exported from Xcode directly: open your .storekit file, go to Editor → Subscription Offers Key and export it. Source
Common Pitfalls Recap
| Pitfall | Fix |
|---|---|
| Introductory offer not applied in payment sheet | Wait 24h after creating in App Store Connect; verify user hasn't already used an intro offer in the group |
getPromotionalOffer fails silently |
Ensure In-App Purchase Key is uploaded and valid; check for INELIGIBLE_ERROR |
| Win-back offer never triggers | Confirm iOS 18+, SDK v5+, product is App Review approved, and IAP key is uploaded |
| Revenue data wrong for offer codes | Don't use Apple's legacy In-App Purchase Promo Codes — use Offer Codes instead |
Eligibility check returns .unknown |
This is expected; Apple's payment sheet is the final arbiter — always show it |
Summary
Apple's subscription offer system is powerful but fragmented: each of the four offer types targets a different moment in the subscriber lifecycle, requires different setup steps, and is triggered differently in code. The unifying requirement is your In-App Purchase Key — get that in place first, and everything else becomes much more straightforward.
RevenueCat abstracts the most painful parts (StoreKit signing, eligibility APIs, transaction validation) and adds observability via Charts and Customer Lists so you can actually measure whether your offers are working.
Sources
- iOS Subscription Offers — RevenueCat Docs
- Free Trials & Promo Offers (eligibility rules) — RevenueCat Docs
- In-App Purchase Key Configuration — RevenueCat Docs
- iOS Product Setup (Introductory Offers in App Store Connect) — RevenueCat Docs
- Charts Overview — RevenueCat Docs
- Customer Lists — RevenueCat Docs
- Error Handling (INELIGIBLE_ERROR) — RevenueCat Docs
- Customer Center Promotional Offers (Apple) — RevenueCat Docs
- Supporting Offers in Paywalls — RevenueCat Docs
- Scheduled Data Exports v5 — RevenueCat Docs
- Apple App Store Sandbox Testing — RevenueCat Docs