Implementing Introductory Offers (First-Period Discount) in Mobile Apps
Introductory Offers are a powerful StoreKit tool for attracting new subscribers. Apple App Store Review Guidelines (Section 4.2 and 5.1) regulate their use. Without correct implementation, the user won't see the offer, and conversion drops. With proper setup, an introductory offer can increase subscription conversion by 30–50%. We guarantee that your paywall will show the offer only to those who are truly eligible. In this article, we'll cover how to configure and check eligibility, integrate with a paywall, and avoid common mistakes.
Types of Introductory Offers and Their Configuration
Apple offers three types: freeTrial (free), payAsYouGo (discount on each period), and payUpFront (one-time payment for multiple periods). The choice depends on the business model: freeTrial for product trials, payAsYouGo or payUpFront for long-term customer acquisition.
| Type | Description | Example |
|---|---|---|
| freeTrial | Free period | 7 days free |
| payAsYouGo | Discount on each period for N periods | First 3 months at a discount |
| payUpFront | One-time payment for N periods | 12 months at a reduced price |
An Introductory Offer is created at the subscription level: App Store Connect → Subscriptions → [Subscription] → Introductory Offers. Specify the type, duration, and price. Important: the offer applies only to users who have never been subscribers of this subscription group. Apple checks this on the server. A common mistake is specifying an incorrect duration: for freeTrial, duration cannot be 0 days. Typical freeTrial durations are 3, 7, or 30 days.
How to Read the Offer via StoreKit 2
First, load the product and check for an introductory offer:
import StoreKit
// Load the product
let product = try? await Product.products(for: ["premium_monthly"]).first
// Check for introductory offer
if let intro = product?.subscription?.introductoryOffer {
switch intro.paymentMode {
case .freeTrial:
let days = intro.period.value
let unit = intro.period.unit
showFreeTrialBanner(days: days)
case .payAsYouGo:
showDiscountedPriceBanner(price: intro.displayPrice, period: intro.period)
case .payUpFront:
showUpFrontBanner(price: intro.displayPrice, duration: intro.subscriptionPeriod)
@unknown default: break
}
}
Why Eligibility Check Is Critical
Apple checks eligibility on the server side — Apple Developer Documentation.
product.subscription?.introductoryOfferexists on every product regardless of eligibility. So an additional check is needed. StoreKit 2 provides access to transaction history:
// Check subscription status via Transaction
for await result in Transaction.currentEntitlements {
if case .verified(let transaction) = result {
if transaction.productID == "premium_monthly" {
userHasBeenSubscriber = true
}
}
}
An alternative is server-side verification via the App Store Server API. The server returns isInBillingRetryPeriod and full history. This is more reliable but more complex. Our golden rule: UI decisions on the client, eligibility validation on the server. This approach eliminates abuse and errors.
How to Implement an Introductory Offer: Step-by-Step
- Create the offer in App Store Connect. Specify type, duration, and price. Check duration — for freeTrial it cannot be 0.
- Load the product and read the offer in code. Use
Product.products(for:)and checkintroductoryOffer. - Check eligibility. Via
Transaction.currentEntitlementsor server API. For iOS 15+, useeligibleForIntroOffer. - Build the paywall UI. Display a banner with offer terms if the user is eligible.
- Test. Use StoreKit Configuration File in Xcode to simulate scenarios without waiting 24 hours.
- Document and hand over to support. Specify which offers are active and how they are displayed.
Using RevenueCat
RevenueCat handles eligibility checking. The code becomes simpler:
Purchases.shared.getOfferings { offerings, error in
if let intro = offerings?.current?.monthly?.product.introductoryDiscount {
Purchases.shared.checkTrialOrIntroductoryPriceEligibility(
productIdentifiers: ["premium_monthly"]
) { eligibilityDict in
let eligible = eligibilityDict["premium_monthly"]?.status == .eligible
}
}
}
According to our data, switching to RevenueCat cuts subscription logic development time by 2–3x and reduces errors by 70%. RevenueCat also automatically handles purchase restoration and notifications.
Displaying on the Paywall UI
A typical paywall conditionally shows the offer:
struct PaywallView: View {
let product: Product
@State private var isEligibleForIntro = false
var body: some View {
VStack {
if let intro = product.subscription?.introductoryOffer,
isEligibleForIntro {
IntroOfferBanner(offer: intro)
.transition(.opacity)
}
SubscriptionButton(product: product)
}
.task {
await checkEligibility()
}
}
func checkEligibility() async {
isEligibleForIntro = await checkIntroEligibility()
}
}
Comparison of Eligibility Check Methods
| Method | Complexity | Reliability | Recommendation |
|---|---|---|---|
| StoreKit 2 (Transaction.currentEntitlements) | Medium | High (iOS only) | For simple apps |
| App Store Server API | High | Maximum | For complex projects |
| RevenueCat | Low | High | Universal solution |
Common Mistakes in Setting Up Introductory Offers
- Incorrect freeTrial duration (e.g., 0 days) — the offer won't be applied.
- Ignoring eligibility — the offer is shown to everyone, causing purchase errors.
- Lack of testing via StoreKit Configuration File — requires waiting 24 hours for re-test.
- Ignoring iOS version:
Transaction.currentEntitlementsis only available from iOS 15. For older versions, use a server API or a library like RevenueCat. - Forgetting to update the paywall when subscription status changes (e.g., after restoration).
What's Included in the Work
- Reading and displaying the introductory offer from the
Productobject (StoreKit 2) - Eligibility check via
Transaction.currentEntitlementsor server validation - Paywall UI component with conditional offer display
- Testing using StoreKit Configuration File in Xcode (sandbox without 24h wait)
- Analytics logging: offer display, conversion, offer type
- Documentation and knowledge transfer to your team
Timelines and Cost
From 3 to 5 days depending on the paywall UI complexity and availability of server-side validation. Cost is calculated individually after requirements analysis. Contact us for an accurate estimate. Get a consultation on implementing subscriptions in your project.
We have over 5 years of experience in developing subscription solutions for iOS and Android. Our certified developers ensure compliance with App Store Review Guidelines.







