Learn how Rewarded Video can help subscription apps monetize non-subscribers, while nudging them one step closer to subscribing.
The primary monetization goal of subscription apps is to convert users into paying subscribers. Of course, not every user who installs will convert. That’s where Rewarded Video comes in.
Rewarded Video for subscription apps is great new opportunity to monetize non-subscribers, while nudging them one step closer to subscribing.
In this article, we’re going to discuss:
Rewarded Video is a type of mobile ad unit where the user watches a full-screen video advertisement in exchange for a reward. Commonly the ads are promoting other apps and providing the user a simple call-to-action to download that app.
Rewarded Video ad units are non-skippable, so users need to stay engaged for the duration to receive the reward. According to Business of Apps, this ad format often commands higher eCPMs. The format is compelling for publishers and advertisers, but also for users who receive a tangible value exchange.
For example, many games use Rewarded ad units. Users are rewarded extra lives, points, and other virtual goods.
Rewarded Video can be a useful compliment to your subscription app, especially if you offer a freemium experience. Here are a few ways you can integrate Rewarded Video into your conversion funnel:
👉Read more: Let Your Fans Support Your App with a Tip Jar
Adding Rewarded Video to your subscription app requires some product decisions as well as the right infrastructure.
For product, consider the following:
For infrastructure, you need two components to deliver this experience:
This sounds like a lot, but fortunately we provide an out-of-the-box solution to make this super easy. Nami provides the Entitlement Engine. Nami also provides integrations with Rewarded Video ad solutions.
Integrations with AppLovin and ironSource offer a Rewarded premium access experience that is easy. It requires minimal setup, limited client-side implementation steps, and no additional backend server-side. You even control how many minutes of premium access a reward is worth from the Nami Control Center.
👉Read more: Promotional In-App Purchase Best Practices
Monetization in the app economy is not easy. Rewarded Video offers app publishers another opportunity to monetize users. For subscription apps in particular, this is a powerful way to nurture freemium users towards your premium experience while also generating some incremental revenue.
To experience Rewarded access in a subscription app, check out Serenity Sounds. To be offered Rewarded access, first try to access a premium sound (anything but the first row). You will be prompted to subscribe via the paywall sheet.
If you dismiss the paywall without subscribing, you may be prompted if a Rewarded Video is available. Alternatively, navigate to Settings (the gear icon) to see if a Rewarded promotional unit shows up. In either case, watch the reward to completion to receive 1 hour of access to the premium sounds.
Do you have a subscription app and are interested in adding a Rewarded experience? If so, we’d be happy to show you how it works and talk about how Nami can help.
if(window.strchfSettings === undefined) window.strchfSettings = {};
window.strchfSettings.stats = {url: "https://nami.storychief.io/en/rewarded-video-subscription-apps?id=1460241903&type=26",title: "Rewarded Video to Unlock Subscription App Opportunities",id: "51b60849-ff21-4408-b48f-9543da3cae59"};
(function(d, s, id) {
var js, sjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {window.strchf.update(); return;}
js = d.createElement(s); js.id = id;
js.src = "https://d37oebn0w9ir6a.cloudfront.net/scripts/v0/strchf.js";
js.async = true;
sjs.parentNode.insertBefore(js, sjs);
}(document, 'script', 'storychief-jssdk'))
We shipped a new SwiftUI app, offering a lifetime IAP and two subscriptions plans powered by Nami. See how to do it.
We decided it was time to build and ship a brand new app using SwiftUI to experience first hand the latest from Apple.
The app, now available on the App Store, is called Serenity Now.
It’s a simple audio player with a SwiftUI interface and AVFoundation under the hood. The app is filled with a collection of sounds for focus, relaxation, and better sleep.
👉Read more: Adding In-App Purchases to SwiftUI Apps
We’re in the monetization business, so we set out to implement our own Nami SDK to offer IAPs and subscriptions.
Here are the three products we want to offer:
Users who buy one of these products gets access to our Serenity Now Premium features. Those features include:
We want users to encounter our paywall during the app experience in three different places:
Now that our goals are clear, let’s see what it takes to accomplish.
SwiftUI apps don’t use the the AppDelegate we have all come to know from years of building iOS apps. Instead, the entry point for SwiftUI apps is a struct conforming to the App protocol.
That protocol has one requirement, which is the implementation of a property called body. Digging into the specifics of body is outside the scope of this article, but suffice it to say that is where you’ll kick off your app’s user interface.
For our purposes, we need a place to configure the Nami SDK. It turns out, we can provide a custom init() method on our App struct for this purpose.
Here we setup our NamiConfiguration object with our app’s unique appPlatformID (found in the Nami Control Center > Integrations > [your Apple App Store integration]
We will need to monitor changes to the the entitlements a user has access to. A change may occur if they buy one of our IAP products, or if they have a subscription that expires.
In Serenity Now, if a user buys any one of the three IAP products, we grant them access to an entitlement called premium_access.
In SwiftUI, we can monitor for user entitlement changes by setting up an ObservableObject. Specifically, we need to register with the Nami SDK’s registerEntitlementsChangedHandler callback to update our ObservableObject’s premium var.
Since premium has a @Published property wrapper, any SwiftUI view’s body will be re-invoked any time the value changes.
This is exactly what we want. If a user buys one of our products, we want any of the SwiftUI views that rely on the value of premium, to be updated. Concretely, this means any view that gates access based upon the premium_access entitlement will update if the value changes.
Next, we want to show our paywall the first time the user launches the app.
In our main SwiftUI view, SerenityNowHome, we run some code in .onAppear that checks UserDefaults for the value of a key didLaunchBefore. If the value is false, it’s the first launch of our app so we tell the Nami SDK to show a paywall if NamiDataSource’s premium var is False.
Next, we want to gate access to certain premium content in our app.
If the user does not have access to the premium content (e.g. NamiDataSource’s premium var is False), we want to present the paywall. Otherwise, we want to show the SwiftUI with the relevant content.
Finally, we want to give users a way to access our paywall from a marketing tile in the Settings section of our app.
In our Settings view, if the user does not have premium access (via our NamiDataSource), we show our NamiUpsellBannerView. If the user taps on it, we show our paywall.
If the user does have premium access, we instead show a link to Manage subscription which takes the user to the system Settings.
👉Read more:What’s New with In-App Purchases at WWDC 21
There is much more we want to do with our SwiftUI app and some advanced use cases we want to discuss in future blog posts. For now, we hope this article shows how is it is to get up and running with some very common use cases for selling IAPs and subscriptions.
You may be wondering about the paywall view itself. The paywall was created via Nami’s no-code paywall designer. The Nami SDK provides a native Swift that is configurable from the Nami Control Center so you can make changes instantly.
The SDK provided paywall integrates seamlessly with our SwiftUI app from both a user interface and usability perspective. If you’re interested in giving Nami a spin for your own SwiftUI app, you can create a free account here.
if(window.strchfSettings === undefined) window.strchfSettings = {};
window.strchfSettings.stats = {url: "https://nami.storychief.io/en/monetizing-swiftui-app-iap-subscriptions?id=1919917807&type=26",title: "Monetizing a SwiftUI App: IAP & Subscriptions",id: "51b60849-ff21-4408-b48f-9543da3cae59"};
(function(d, s, id) {
var js, sjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {window.strchf.update(); return;}
js = d.createElement(s); js.id = id;
js.src = "https://d37oebn0w9ir6a.cloudfront.net/scripts/v0/strchf.js";
js.async = true;
sjs.parentNode.insertBefore(js, sjs);
}(document, 'script', 'storychief-jssdk'))
Apple Fiscal Calendar 2022 starts on September 26, 2021 and ends on September 24, 2022. Learn how this is used for app developer payments.
Apple Fiscal calendar 2022 starts on September 26, 2021 and ends on September 24, 2022. Apple uses this calendar for the company’s accounting as well as reporting and payments due to app developers.
Here is the 2022 calendar by quarter and by period:
Apple pays developers proceeds for app or in-app purchase sales within 45 days of the last day of the fiscal month in which the transaction was completed.
Payments are made only if the following is true:
Apple consolidates proceeds so you can expect a single payment to your bank each fiscal period.
👉Read more: Accrued Revenue
Here is an online Apple payout calendar resource you can bookmark that is updated for the current fiscal year. It’s also available in an downloadable PDF format.
if(window.strchfSettings === undefined) window.strchfSettings = {};
window.strchfSettings.stats = {url: "https://nami.storychief.io/en/apple-fiscal-calendar-developer-payout-schedule?id=1730333766&type=26",title: "Apple Fiscal Calendar 2022 & Developer Payout Schedule",id: "51b60849-ff21-4408-b48f-9543da3cae59"};
(function(d, s, id) {
var js, sjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {window.strchf.update(); return;}
js = d.createElement(s); js.id = id;
js.src = "https://d37oebn0w9ir6a.cloudfront.net/scripts/v0/strchf.js";
js.async = true;
sjs.parentNode.insertBefore(js, sjs);
}(document, 'script', 'storychief-jssdk'))
Server side validation of Apple App Store receipts can be tricky. We show how to build a simple server to handle the basics to verify receipts.
One of the more tricky parts of adding in-app purchases to your Apple App is verifying receipts on a server. Apple strongly recommends that you use a server to parse and validate receipts. This can helps to reduce the risk of fraud. But how do you handle things like the call to verifyReceipt or SKReceiptRefreshRequest? We’ll break this all down by showing you how to create a basic server to do iOS receipt validation.
Apple’s StoreKit framework provides a mechanism for selling in-app purchases or subscriptions through the App Store.
An essential artifact, the App Store receipt, is used to verify purchases and understand purchase activity. In this multi-part series, we will go beyond Apple’s documentation to demystify the receipt by coding a simple Python receipt validation script then progressively building out a server-side receipt validation app using Python, Flask, and Docker. This will make it easy for you to modify and deploy to a cloud service like AWS, GCP, or Azure.
Before we jump into the Python, let’s quickly talk about how to access the receipt on the client-side.
👉Read more: iOS App Shared Secret
The primary way you will access an App Store receipt is from your app code.
From your Xcode project, you can use the Bundle.main.appStoreReceiptURL to access the Base 64 encoded receipt data.
Here’s an example:
The code above is not guaranteed to return a receipt. Whether a receipt is returned depends on the app build:
Now that you know how to retrieve the encoded receipt, next we’ll talk about receipt validation.
Now that you know how to retrieve the encoded receipt, next you need to validate it.
It’s possible to validate a receipt from the client-side or the server-side. Server-side receipt validation is more complicated, but the benefits are numerous. Especially if you offer auto-renewing subscriptions, server-side validation is strongly encouraged. You and read more about choosing a receipt validation technique from Apple’s documentation.
An App Store receipt provides a record of the sale of an app or any purchase made from within the app, and you can authenticate purchased content by adding receipt validation code to your app or server. - Apple Developer Documentation
For this series, we’re going to employ server-side receipt validation. To do that, we're going to lean on Python, Flask, and Docker to consume an encoded receipt passed up from your app’s client code. Then, we’ll dig deeper into interpreting the decoded receipt, as well as what response to send back to your client.
If you’re ready to head straight into the details of a decoded receipt, jump to our definitive guide, an element-by-element breakdown of a decoded receipt.
First, let’s build a rudimentary script to to better understand the receipt verification workflow.
Apple provides a verifyReceipt service to be used for server-side receipt validation. The basic request and response pattern for this service is pretty straightforward, which we can demonstrate with a simple Python CLI script that does the following:
Let’s get started!
We will be using Python 3 with standard libraries, so the first thing we need to do is import the modules we will need.
Next, we need create a global variables for the verifyReceipt endpoint. There are actually endpoints: Sandbox (sandbox.itunes.apple.com) & Production (buy.itunes.apple.com). Our script will support receipts from both environments, so let’s set global variables defining each endpoint.
To determine which endpoint needs to be used, you need to know what kind of app build was used to make the purchase.
Since this script will take in command-line arguments, let’s create a simple method to handle sending the verify receipt request to Apple. Our method will accept several arguments including whether or not we should use the Sandbox endpoint.
Best Practice: Apple recommends first sending a receipt to Production. If the receipt is for Sandbox, the response will contain a status field with the value 21007. This is your signal to try the Sandbox endpoint instead.
Next, we need to construct a valid requestBody which consists of a JSON data structure contains the Base 64 encoded receipt and a password field which is required for auto-renewable subscriptions. To locate your app’s hexadecimal shared secret via App Store Connect, check out this guide.
Now we are ready to send the HTTP POST request to Apple.
If the request was successful, you will receive a HTTP 200 OK response code. This means we can expect to receive a JSON responseBody. The first thing we need to inspect from the responseBody is the status field. If status is 0, the receipt is valid and many other fields will be present in the responseBody as well. We’ll dive deeper into the various elements of the receipt later in this series.
For now, we will use the status value to print a message explaining whether the receipt was validated or not. Just in case we don’t receive that HTTP 200 OK we were expecting, we’ll also catch and print any unexpected HTTP response codes here as well. In production, you can expect to see non-200 responses from Apple so you will need to add logic to handle this and retry if need be.
Here are the most common status values you will encounter:
There are others which are much more rare that you can read about here.
Now we’re ready to prepare our command-line arguments. We expect a file containing an encoded receipt to be passed to this script. If we don’t at least see one command-line argument, let’s print a helpful message.
Let’s try to read in the encoded receipt data from the file path provided in that first argument.
We need to see if any optional command-line arguments were provided. This code supports a --secret argument to pass in the hexadecimal shared secret discussed previously. Additionally, --use_sandbox tells the script to use the Sandbox verifyReceipt endpoint. Otherwise, it will default to Production.
Finally, we construct our verify_receipt method call.
You now should have a good sense for what’s involved to send a receipt validation request to Apple and the basic response codes you can expect to encounter. Head on over to GitHub for the complete source code and examples for the Python CLI covered in this tutorial.
In the next part of this series, we dig deeper into the receipt responseBody.
Until next time, happy validating!
👉Read more: App Store Verify Receipt Definitive Guide
Error Code 1, also known as User Canceled, occurs when a user actively backs out of an in-app purchase or subscription flow on Google Play. This is a common issue developers encounter during user testing or in real-time use, and while it’s not an error in the app’s code, it’s critical to handle this response to ensure a smooth user experience. Understanding how to address this error can help retain users and provide a seamless purchasing experience.
Error Code 1 is triggered by user action—specifically, when the user decides not to proceed with the purchase. This can happen for various reasons, such as changing their mind, encountering issues with the payment method, or facing unexpected charges. While this error does not indicate a bug, it’s important to handle it correctly to offer an intuitive user experience and potentially encourage the user to try again.
By anticipating why users might cancel, developers can refine the user journey and minimize the occurrence of this code.
Error Code 1 often arises in the following situations:
Error Code 1 - User Canceled, is a common response that stems from user decisions to back out of the purchase process. By following these best practices—implementing cancellation callbacks, simplifying the purchase flow, displaying transparent pricing, using soft reminders, and gathering feedback—developers can improve the purchase experience and reduce cancellations.
Nami’s low-code solutions make managing and optimizing in-app purchases seamless, allowing you to avoid complex purchase flow setups and focus on delivering a frictionless app experience. Learn how Nami can handle in-app billing for you at NamiML.
Error Code 7, commonly known as “Item Already Owned,” appears when a user attempts to purchase an in-app item or subscription that is already owned. This issue often arises during testing and can prevent developers from simulating new purchases or upgrades. Handling this error is crucial for a smooth user experience, particularly when managing subscription renewals and repeated purchases.
The root cause of Error Code 7 is that Google Play detects the item in question as already owned by the user. This situation commonly occurs with non-consumable purchases (such as subscriptions or one-time purchases) when previous transactions are not properly acknowledged or consumed. During testing, the error may persist if the same item is repeatedly tested without resetting ownership data or consuming the item, creating conflicts within the billing flow.
Understanding this can help developers clear ownership data or adjust purchase flow to avoid errors.
This error is frequently encountered in the following situations:
Error Code 7 - Item Already Owned, is typically a result of unhandled ownership data or incomplete consumption in the app’s purchase flow. By implementing the above solutions—consuming items, querying purchase history, clearing cache, canceling subscriptions, and using fresh test accounts—developers can effectively resolve this error and optimize user experience.
With Nami’s low-code solutions, developers can bypass these issues entirely, allowing Nami to manage in-app purchases and updates automatically, so you can focus on building your app without billing-related headaches. Discover Nami’s solutions at NamiML.