Streaming is available in most browsers,
and in the WWDC app.
Architecting for subscriptions
Learn how you can build simple entitlement logic to enhance the customer experience. We'll dive deep into key concepts and provide guidance for architecting your systems to accurately entitle service. You'll learn best practices for subscription features and how to craft the best customer experience throughout the subscription lifecycle.
Hello and welcome to WWDC.
Hello, everyone. Thank you for viewing "Architecting for Subscriptions." My name is Michael Gargas, a technical advocate on the App Store Commerce team, and I'm excited to introduce some new concepts around how to build and maintain a subscription service on the Apple platform. We want to structure this to be helpful not just for business and engineering, but also for your server-side teams and data analysts.
Whether you've been offering subscriptions within your app for years or are just getting started, this session will provide valuable information for constructing your system's entitlement architecture. From there, you'll see ways to build or reconstruct your server-side systems to take full advantage of the new features which Apple will provide over time.
To build a successful subscription platform, it's important to understand the journey a subscriber can take. We can then define subscription entitlement, craft custom logic, and put it all into practice by utilizing an entitlement engine to provide a tailored experience for those subscribers.
So let's take a deeper look at the subscriber journey and the different complex states that can result from different subscriber actions.
In the past, access may have been granted based on one or two receipt fields, such as product ID and expiration date, determining: Is this user or subscriber active or not? As we've launched new features like Billing Retry and Grace Period, these combinations of receipt fields have become more and more complex, and understanding them requires deep knowledge of the app receipt itself.
Let's take a look at an example subscription in a calendar view. Here, we have a one-month product which was purchased on May 1st. Let's take a deeper look and see how different subscriber actions can affect subscriber states.
For a basic subscription that was purchased on May 1st, Apple will send you, the developer, a server-to-server notification signifying an initial buy has taken place. If no subsequent actions are taken by the subscriber, we will continue to renew the subscription on the 1st moving forward. But let's say that the subscriber elects to cancel their subscription.
Here, we see the example subscription purchased again on the 1st. However, the subscriber has navigated to the App Store Manage Subscription setting and elected to cancel. It's at this point that Apple will send you, the developer, a server-to-server notification letting you know that that subscriber has disabled their auto-renewal status.
If no subsequent actions are taken, this user will voluntarily churn from your subscription product. This will be signified via the expires_date and expiration_intent fields in the receipt response.
In this example, what if Apple is unsuccessful in charging the user's card for a subsequent renewal? Here again, we see the initial buy notification on the 1st, but Apple was unsuccessful in charging the card on the subsequent anniversary date. The user, however, navigated to the App Store's Payment Information settings and updated their payment method on file. It's at this point that Apple will deliver you, the developer, a server-to-server notification signifying that the subscriber has renewed, and we will continue to renew that subscriber on the 15th moving forward. However, what if you, as a developer, have Grace Period enabled? In this example, again we see the subscription purchased on the 1st, and we were unable to renew the card on the subsequent renewal date. However, with Grace Period enabled, any recoveries which happen within a 16-day period will keep billing date continuity. So as you can see, Apple will continue to renew that subscriber on the 1st moving forward. It's important to understand that each subscriber journey is unique. It's for this reason that we need to understand all of the receipt fields, the signals Apple sends as a result of customer actions and the different states that may be a result of those specific customer actions.
These examples illustrate how quickly a subscription state becomes more than active versus inactive. Basic actions like upgrading or downgrading, and even crossgrading, and other complex billing states, like Billing Retry and Grace Period, also present messaging opportunities or actions which can help provide a better customer experience, reduce churn and maximize conversion.
In previous sessions, we've highlighted a wide range of tools and features that can be used to enhance your subscription service. Whether it's the StoreKit framework that helps process payments, server-to-server notifications or even enhanced receipt data, each of these lay the groundwork for building an engaging subscription service on the Apple platform.
I highly recommend that you check out these additional sessions to ensure you are up-to-date on all the existing and new features which will be available for integration in your applications. So in order to respond to customer actions accurately, it's imperative that we take a deep dive into the subscriber state.
Here we see that identifying the subscriber state is a key step in the purchase process. This helps you, the developer, best understand the experience that you want to provide to your end users. Whether it's presenting a subscription offer or un-entitling service for the user because they've been refunded, understanding the subscription state is key.
When we think about subscription state, it's important to know where the subscriber has been, where they are at, and what event may take place in the future.
Deep knowledge of the data within the app receipt is key to understanding subscription states. Each renewal event in the receipt is a static entry showing that subscriber's state at that moment in time.
State can then be inferred by looking at combinations of these different receipt field values.
Using these states, you can then decide to tailor your experience for that subscriber and take any relevant actions within the app experience.
Taking a deeper look, it is obvious that there are many different states which a subscriber can land in. These states are a combination of different receipt values, such as an expires-date value being set in the future and auto-renew status being a value of one, representing active, auto-renew on.
For each of these states, we've also defined relevant substates to show what type of offer the user is consuming, such as a free trial or even a subscription offer.
Now that we've established a better understanding of the complexity behind subscriber state and substate, let's see how these can manifest in the real world for subscribers and what relevant type of actions or messaging you, as a developer, may want to take as a result. So let's walk through these five different examples of complex states and the resulting actions to take.
When looking at a subscriber and trying to decipher the state, we have to look at the most recent receipt data.
At the surface, we may only see "active" if we're only looking at expiration date. When looking at a subscriber state and trying to decipher that state, you, as a developer, have to look at the most recent receipt data. On the surface, you may just see "active" if only the expiration date is used.
However, if we look even just a little bit deeper, we can see an opportunity for a retention subscription offer based on that user's auto-renewal status and the current subscription product. In this case, the subscriber is currently on a subscription offer. As a developer, you may want to attempt to retain them with a follow-up subscription offer.
Using that same logic, if we dive deeper here, we can see a user who has expired, but is in actuality in a Billing Retry state, signifying that we, Apple, are still attempting to collect payment for a subscription renewal. In this example, as a developer, you may want to surface a persistent banner which deep links the subscriber to the App Store's account to update their payment information.
When using the Grace Period example, we can see another opportunity to identify the user is in a grace period via these specific receipt fields, and offer maybe a countdown of days remaining for available service. This can also deep link to that subscriber's payment information in the App Store so that they're able to update and recover their subscription.
Another opportunity to provide a tailored experience is to win back users who have already expired. In this example, we're looking to see how the user expired, what product they were initially subscribed to, and providing a push notification merchandising a current win-back marketing offer.
It's important to note that this push notification will still need to deep link that user to an appropriate payment screen within your application.
And lastly, as a developer, you may want to provide subscribers a better experience by merchandising potential upgrade opportunities.
In this example, you may notice the user has purchased a monthly product and renewed for multiple consecutive periods. By offering an upgrade to an annual subscription, you can provide a discount and reward your most loyal subscribers.
Now we have identified five states and substates, but as you can see, there isn't just one state for active versus inactive. There are many more potential outcomes based on subscriber actions.
Understanding this is a key factor in defining subscription entitlement. To walk you through the details of entitlement, I'd love to welcome my colleague Garrett. Hi, my name is Garrett Cox, and I'm a solutions engineer for the App Store. Michael described the subscriber journey, and as we've seen, there are many potential states a subscriber can encounter. The entitlement process needs to account for these potential states. Let's start by defining the components that make up subscription entitlement.
While access to content is the fundamental basis of subscription entitlement, the scope of access will inevitably be broad. Rather than just unlock content, access might vary based on geographical availability, billing states and unique levels of service. Before we present products to the user, we must determine whether they qualify. This also applies to the upgrade and downgrade options we might present. How and when you display products changes based on the user's eligibility for discounted pricing like free trials or offers.
The App Store determines certain eligibility criteria, such as for free trials, but you can control eligibility for subscription offers based on your business needs.
Lastly, your messaging to the user can be more meaningful than simply communicating an expiration date. A tailored experience means timely communication of promotional messaging or critical billing issues which change throughout the subscriber journey.
Given this definition of subscription entitlement, you're going to need to build the server-side logic which digests all of the subscription data encompassing the subscriber journey. For now, we'll be referring to this as the entitlement engine.
This digested data is then used to calculate the user's service entitlement. The engine needs to support changes to your subscription offering as well as the billing states a user might encounter.
So more specifically, our engine takes receipt data and any other app insights to calculate and respond with correct entitlement info.
So before I walk through how we, as subscription developers, actually need to code this entitlement engine, I want to let you know that we will be providing sample code in Node.js in an article with this session to represent the parts of this process and help you get started in building your entitlement engine. For now, I'll be addressing these steps as if we're building this entitlement engine together.
Here's a more detailed overview of what the entitlement process looks like using our engine. Subscription data, including receipts and server notifications, are passed into the engine. The output is a simplified JSON payload that you use to entitle service and update your user database.
The app receipt is our source of truth for inspecting transactional data, so it's only fitting our first step is to validate the authenticity of each receipt. This phase should include fetching the newest receipt info from the App Store's verifyReceipt end point if the receipt we are starting with is out-of-date.
We want to ensure we're making entitlement decisions using up-to-date information that isn't outdated.
Optionally, we can fetch from our systems other relevant data or contextual details unique to the subscriber not found in the receipt.
If you're providing a subscription across multiple apps or platforms, this phase should include passing in the subscriber's status that exists outside of the app receipt to ensure they aren't locked out or presented with another subscription to purchase.
Next, we'll synthesize the relevant data we have gathered.
This phase is really the powerhouse of the engine because we want to condense and convert the information we have into actionable insight.
Given that information is interspersed across multiple arrays and fields in the in-app receipt, like the latest_receipt_info array and the pending_renewal_info array, we want this process to thoroughly check all of the relevant fields that shape entitlement.
We want our logic to iterate over the data, building data objects that we will eventually include in our final response.
Our focus here is to produce and attach any key details that will aid in determining where in the subscriber journey a user might be.
So for example, we'll want to organize the response by subscription product and include essential information. This key data is valuable to any subscription service like product ID, trial consumption or expiration date. We'll revisit the entitlement code for now.
Additionally we can add insights outside of the receipt that still might influence the entitlement process. For example, hours watched or an upcoming event the subscriber might be interested in. We've now condensed the data down to the meaningful details to distinguish one unique cohort of subscribers from another. At first the cohorts should be defined to cover all the general subscription billing states. These separate cohorts of subscriber states will allow us to craft unique experiences based on the location of the subscriber in the journey.
As we organize the subscriber journey into varying states, we want to distinguish subscribers within the states Michael enumerated.
So for this example, I'm simply assigning unique values to each of those states. These could be modified or enhanced to fit the needs of your subscription.
And then I also do the same with the substates to add uniqueness to the specific subscription products by assigning them a decimal value.
This way, the combination of the two can be paired together to represent a range of cohorts.
For a simple approach, the positive values represent cases where we would unlock access, and the negative values represent subscription states where the service wouldn't be provided for a given product ID.
Even though we define generic access with positive and negative values, we can enhance the entitlement process to pay attention to unique cohorts.
Combining these values, we now have an entitlement code to represent the state of a product. We can send this value to the client or store it server-side. The code can be treated as a simple signal to unlock the service since it is positive. Or in this example, the value could be handled as a custom action since its unique insight represents an active subscriber that has disabled auto-renew during the free trial period.
Now that we've identified the cohort of a subscriber, we'll use that to determine the entitlement info we want to include.
In this phase, we focus on generating and attaching added data that suits the unique cohort of the subscriber.
This phase can also include taking specific business actions that apply to each unique cohort. Here's how.
We built our response object to contain the essential keys we'll need for entitlement and populated those keys with the values pertaining to subscription entitlement. We can take this representation and, with a few statements, tailor the entitlement to cover complex states like the ones Michael had mentioned.
So for the retention example where the subscriber just disabled auto-renew...
an if statement including the entitlement code 4.0 and the specific product can then be used to attach a retention message or even an offer. The nuanced state of Grace Period Michael mentioned must be used to unlock service throughout the grace period, but since we already have defined this unique code, we can use that state to add logic covering the more niche action to prompt the user to update their billing information in order to prevent unwanted loss of service. And since Grace Period is slightly different than Billing Retry, we can have a similar goal of updating billing information while providing limited access instead.
In this example, we can use the entitlement info to present a custom offer to the user that has opted to cancel service.
Even for our loyal subscribers, we can use this entitlement code, the number of renewals and the subscription group ID, to suggest upgrading to an annual product.
So we've made it to the final step and generated all the necessary entitlement info.
We want to route this data to update our database so it accurately is synced and representing the state of the user.
And if it was a user's device that was requesting this entitlement data, we should send this calculated data back securely to the device. Consider adding something like a JSON Web Signature to prove the data came from your server. We just dove deep into crafting the entitlement engine logic, so now let's zoom out some and get a sense of how to put the engine to use within our system's architecture.
Let's keep in mind some essentials.
A highly available entitlement engine will allow us to compute and respond with the latest entitlement details the moment a change happens. This availability, paired with the coverage of all the subscription states, ensures we accurately entitle subscribers with the proper experience.
While responding with accuracy is the mission, we want to position this process to be fail-safe from unexpected circumstances.
And lastly, we've built our engine in a manner that we can leave room to support future features or changes to our subscription business that may happen later on.
We've covered how to build the engine to validate, authenticate and properly entitle for the subscription experience. Here's an overview on how we can maximize the engine in our server architecture.
This approach is really powerful, even when just getting started with subscriptions.
With just a basic implementation that isn't using notifications or storage, we can still support many of the subscription features Apple has to offer, like subscription offers. Also, having this engine running server-side means any errors or updates in our entitlement logic can be fixed, tested with receipts or mocked receipt data and then quickly deployed. That way, new inbound requests will resolve to properly entitle access without relying on a client update. The catch here is that we need the devices to be sending the receipts to be processed in each request. Without storing the receipt data, we also won't have a way to support web or other platforms, but this approach is a promising start and even a reasonable fallback in the event that we have some problems with our storage or for some reason missed processing App Store notifications.
But we can easily iterate from here. By adding persistent storage, it now becomes a matter of routing the resulting entitlement info and receipt data to the persistent storage. Getting over that hurdle means we can then grant access via the web and off-platform. But at this point, we should add an end point to receive and process the App Store Server notifications. Our server can then be notified the moment a change is made to the subscription status rather than status polling the verifyReceipt end point.
With this implementation, we also have multiple ways to support failure. If notifications are missed or not processed, use the persistent storage receipt data to maintain accuracy. Even if our storage data is inaccurate, we can update our entitlement logic. That way we can route the data directly to the entitlement engine and entitle service until we resolve the problems with our storage. So if there's anything you take away from this session, it should be that building a responsive entitlement process will allow you to tailor to the complexity of the subscriber experience.
From there you can then iterate to better support the subscriber journey.
Thank you for attending this session, and I hope you enjoy the rest of WWDC.
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.