NEW: Promotional offers

Use Apple promotional offers and Google developer determined offers to create win-back campaigns

The feature described in this section is supported on the following versions and above:

  • iOS: 4.0.1

  • Android: 4.0.0

  • ReactNative: 4.0.1

  • Flutter: 4.0.0

If you use a prior version of the SDK your users won’t see a discount and will purchase at the regular price.

In this article we are going to describe the process to create promotional offers on AppStore Connect, Google Play Console and Purchasely Console Promotional offers can be used to offer a specific discount to current or past subscribers. It is a great way to retain or win-back a customer. You will be able to set up as many as you want by creating specific paywalls with those offers.

You are responsible for the eligibility of those promotional offers, you must create a specific paywall and display it only for the users you want to target (see Purchasely console below for more details)


Console configuration

Purchasely must have an Apple certificate to sign promotional offers, the configuration is exactly the same than for StoreKit 2, so if you already did it you can skip that part and move to AppStore Connect configuration

Allowing Purchasely to sign promotional offers requires a few steps. Once completed, you can update your application settings in Purchasely console.

Enable App Store Connect API access

  • Go to "Users and Access"

  • Select "Keys" under the "In-App Purchase" section

  • Click on the "+" button to generate a new API key

  • Choose a name for the key and click "Generate"

  • Download the API key file (.p8), and note the Key ID and Issuer ID. Keep the file secure, as you won't be able to download it again

Setup on Purchasely Console

  • Go to "App Settings"

  • Select Apple App Store" under the "Store configuration" section

  • Fill in the Private Key Id from the key you generated

  • Upload your Private Key File (.p8)

  • Fill your Issuer Id

  • Click on Save in the top right corner

Once this configuration is set, Purchasely SDK 4.0.0 and up will be configured to use StoreKit 2 as default. If you wish to remain with Store Kit 1, which also works with promotional offers, you need to force it in the start() method of our SDK.

AppStore Connect configuration

A promotional offer is only available for current and previous subscribers of the selected subscription. You can create it from AppStore Connect in the same page where you manage your subscription price and introductory offers

From that page select Promotional Offers tab and then click on the + button to create a new one

Setup the discount you wish to offer, it can be a: - free (example: 3 months free then $9,99/month) - pay up front (example: $14,99 for 3 months then $9,99/month) - pay as your go (example: $4,99/month for 3 months then $9,99/month)

Once created, copy the id you have set for this offer to paste in Purchasely Console


Promotional offers for Google are Developer Determined Offer which can be set on your base plans for a subscription. It requires the usage of Google Play Billing v5 which is included in Purchasely SDK 4.0.0

Developer determined offer are available for all your users all the time. As the name suggest, it is up to you to decide when to make this offer available. Unfortunately Purchasely SDK cannot know the offer type, so by default this offer will be presented to all your users by our SDK. To avoid this, you can add the tag ignore-offer (see below for more details)

To create an offer, go to your application subscription and select Add offer

Then chose the base plan to apply this offer to

Your offer must contain the following information: - Offer id: you can chose anything, it will be the one you will fill in Purchasely console - Eligibility criteria: Developer determined - Tags: ignore-offer (see notice below) - Phases: you can add up to 2 phases, one free trial and one price discount

To avoid offering these offers to all your users, we strongly suggest to add the tag ignore-offer to all your developer determined offers so that Purchasely SDK won't display it to your users unless explicitly defined in a paywall as a promotional offer

Purchasely Console

When your promotional offer has been created on AppStore Connect and/or Google Play Console, the final step is to declare it in Purchasely Console to use it with your paywall First edit the plan where you want to declare you new promotional offer. The plan MUST be the App Store or Play Store product that you used to declare your offer

Set a name, an identifier for Purchasely and the identifiers you have set in AppStore Connect and Google Play Console. Finally click Save to apply your changes

Then you can create a paywall for your offer, we have created a new action button for that: Winback/retention offer You need to select your plan and offer to be applied as the action for this button You can use the field "Offer" of the different labels to set offers tags like OFFER_PRICE and OFFER_DURATION (same principle than trial offer)

You are responsible for displaying this paywall to users you want to target, so you should create a specific placement for them. You can also target them using Audience

We will send specific events to your webhook or external integration: PROMOTIONAL_OFFER_STARTED



The information about the promotional offer will also be in the payload of events like the one above and ACTIVATE


Trigger the purchase of an offer

Purchasely handles automatically the purchase from a Purchasely paywall but if you are displaying your own paywall or purchase button, you may want to trigger the purchase with Purchasely. You can do it really easily by providing the PLYPlan and PLYOffer you want to use for the purchase

// First get the plan you want to purchase
Purchasely.plan(with: "planId") { plan in
// Success completion
} failure: { error in 
// Failure completion

// Retrieve offer id
let promoOffer = plan.promoOffers.first(where: { $0.vendorId == promoOfferVendorId })

// Then purchase
Purchasely.purchaseWithPromotionalOffer(plan: plan,
                                        contentId: nil,
                                        storeOfferId: promoOffer.storeOfferId) {                    
// Success completion       
} failure: { error in
// Failure completion                    


// We also offer the possibility to sign your promotional offers 
// if you want to purchase with your own system
Purchasely.signPromotionalOffer(storeProductId: "storeProductId",
                                storeOfferId: "storeOfferId") { signature in            
// Success completion
} failure: { error in
// Failure completion

Retrieve the offer to purchase in observer mode

When you are using Purchasely in paywallObserver mode, you can retrieve the offer from our paywall action interceptor, sign it (iOS only) and do the purchase with your system

On iOS, Purchasely anonymous user in lowercase is required as applicationUsername with StoreKit1 or appAccountToken with StoreKit2 Please look at sample code below for more details

Purchasely.setPaywallActionsInterceptor { [weak self] (action, parameters, presentationInfos, proceed) in
	switch action {
		// Intercept the tap on purchase to display the terms and condition
		case .purchase:		
			// Grab the plan to purchase
			guard let plan = parameters?.plan, let appleProductId = plan.appleProductId else {
			let offer = parameters?.promoOffer
			// sign the offer
        		Purchasely.signPromotionalOffer(storeProductId: appleProductId,
        						storeOfferId: offer?.storeOfferId) { signature in
            		// Success completion
       		 	} failure: { error in
            		// Failure completion
			// Purchase with signature
			// Using StoreKit1
			// Using StoreKit2
			// Finally close the process with Purchasely

func purchaseUsingStoreKit1(_ plan: PLYPlan) {

            // First step: Get SKProduct using your own service
            // Example
            let request = SKProductsRequest(productIdentifiers: Set<String>([plan.appleProductId ?? ""]))
            request.delegate = <Your delegate> // Get Product in the `productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse)` method
            // Second Request payment
            guard SKPaymentQueue.canMakePayments() else {
                return nil
            let payment = SKMutablePayment(product: product)
            payment.applicationUsername = Purchasely.anonymousUserId.lowercased() // lowercase anonymous user id is mandatory
            if let signature = promotionalOfferSignature, #available(iOS 12.2, macOS 10.14.4, tvOS 12.2, *) {
                let paymentDiscount = SKPaymentDiscount(identifier: signature.identifier,
                                                        keyIdentifier: signature.keyIdentifier,
                                                        nonce: signature.nonce,
                                                        signature: signature.signature,
                                                        timestamp: NSNumber(value: signature.timestamp))
                payment.paymentDiscount = paymentDiscount

func purchaseUsingStoreKit2(_ plan: PLYPlan) {
    if #available(iOS 15.0, *) {
                Purchasely.signPromotionalOffer(storeProductId: plan.appleProductId,
                                                storeOfferId: plan.promoOffers.first?.storeOfferId,
                                                success: { promoOfferSignature in
                    Task {
                        do {
                            let products = try await Product.products(for: ["storeProductId"])
                            var options: Set<Product.PurchaseOption> = [.simulatesAskToBuyInSandbox(<Bool: true for testing>)]
                            let userId = Purchasely.anonymousUserId.lowercased()
                            if let decodedSignature = Data(base64Encoded: promoOfferSignature.signature) {
                                let offerOption:Product.PurchaseOption = .promotionalOffer(offerID: promoOfferSignature.identifier,
                                                                                           keyID: promoOfferSignature.keyIdentifier,
                                                                                           nonce: promoOfferSignature.nonce,
                                                                                           signature: decodedSignature,
                                                                                           timestamp: Int(promoOfferSignature.timestamp))
                            if let product = products.first {
                                let purchaseResult = try await product.purchase(options: options)
                }, failure: { error in
            } else {
                // Fallback on earlier versions

Last updated

© Purchasely 2020-2023