Implementing Free Trial Subscription in Mobile Application
Free trial—most converting tool for subscription apps, and simultaneously source of many chargebacks and negative reviews. "I didn't know I'd be charged"—typical 1-star review after trial ends. Technically correct implementation solves this before user complains.
StoreKit 2: Introductory Offer
On iOS, trial implemented via Product.SubscriptionOffer with paymentMode = .freeTrial. Product created in App Store Connect: Auto-Renewable Subscription → Add Introductory Offer → Free → N days/weeks. In code:
let product = // Product from prefetch cache
if let intro = product.subscription?.introductoryOffer,
await product.subscription!.isEligibleForIntroOffer {
// show "7 days free"
showTrialCTA(days: intro.period.value)
} else {
// user already used trial—standard price
showStandardCTA(price: product.displayPrice)
}
isEligibleForIntroOffer—async call to App Store, done at prefetch. User who already used trial for this subscription group—not eligible. Showing "7 days free" then charging immediately—path to bank dispute.
Android: Free Trial in Play Billing Library 6+
val params = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(
listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.setOfferToken(trialOfferToken) // from productDetails.subscriptionOfferDetails
.build()
)
).build()
billingClient.launchBillingFlow(activity, params)
offerToken must be selected from subscriptionOfferDetails—take one containing FREE_TRIAL pricing phase. If trial unavailable for user (already had)—take base plan offer token.
Transparency as Mandatory Requirement
Apple requires explicit auto-renewal conditions near purchase button. Standard wording below CTA button:
"7 days free, then $9.99/month. Subscription auto-renews. Cancel anytime in App Store settings."
Font size no less than 12pt. Ignoring this—not just bad reviews, but rejection on review or removal on user complaints.
Besides text button: reminder push 1–2 days before trial end—UNNotificationRequest with fireDate = trialEndDate - 2 days. "Your trial expires day after tomorrow" with deep link to Manage Subscription. Reduces chargeback rate because user forewarned.
Server-Side Trial Validation
Client shouldn't be truth source on trial status. After transaction.finish() on iOS—server validation via App Store Server API. Response contains inAppOwnershipType, offerType, offerIdentifier—server knows it's trial transaction, sets trial_ends_at in database.
App Store Server Notifications V2 event DID_RENEW with subtype: INITIAL_BUY means trial-to-paid conversion. EXPIRED with subtype: VOLUNTARY—user cancelled before trial end. On each event—update database status and push user.
Grace Period and Billing Retry
Subscription didn't renew due to card issues—not cancellation. StoreKit 2 sends GRACE_PERIOD_STARTED notification. Client receives from server subscription_status: grace_period—show banner "Payment issue" with deep link to update payment method (openURL → App Store subscription management). Don't block access during grace (up to 16 days).
Trial Conversion
Average trial-to-paid conversion: 20–40% depending on category and trial length. 3-day trial converts worse than 7-day—user doesn't form habit. 14-day converts roughly like 7-day but operator pays via extended no-revenue period.
A/B test trial length—mandatory. Firebase Remote Config manages which offerIdentifier to use (3 vs 7 vs 14 days). Different App Store Connect offers → different offerIdentifier → Remote Config determines which to show to user segment.
Timeline Estimates
Free trial with transparent conditions, reminder push, server validation, grace period handling, Remote Config A/B: 3–5 working days with ready StoreKit 2 / Play Billing setup.







