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

  1. In App Store Connect, go to Users and Access → Integrations → In-App Purchase.
  2. Click Generate In-App Purchase Key and download the .p8 file — you get one shot at downloading it. Source
  3. In the RevenueCat dashboard, navigate to your App Store app under Apps & Providers → In-app purchase key configuration.
  4. Upload the .p8 file, 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 checkTrialOrIntroductoryEligibility on 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. Use getPromotionalOffernot 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 Type to break down totals into: Introductory Offer, Promotional Offer, Offer Code, Win-Back Offer, or no offer.
  • Filter by Offer Type = offer_code then Segment by Offer to 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