Learn how you can make your in-app purchase experience even better on iPhone, iPad, Mac, and Apple Watch. We'll take you through enhancements to StoreKit 2 and App Store Server API, and explore improvements to App Store Server Notifications. Discover how you can verify app purchases with App Transaction API, add properties to your StoreKit models, incorporate SwiftUI-friendly APIs and StoreKit Messages, and preserve applicationUsername in transactions. For those working server-side, we'll show you how to make the most of App Store Server Notifications, additional ways to retrieve user transaction history, and recovery steps when your server experiences an outage.
♪ Mellow instrumental hip-hop music ♪ ♪ Dani Chootong: Hello, and welcome to "What's new with in-app purchase." I'm Dani, and I'm an engineer on the StoreKit team.
Today I'll be presenting with my colleague Ian, and we'll be going over the new improvements we're bringing to in-app purchase this year.
Last year, we introduced StoreKit 2, a set of new APIs designed from the ground up to make it simple to integrate in-app purchases.
StoreKit 2 uses modern language features, including Swift concurrency using the async/await pattern.
On the server side, we complemented these new StoreKit features with an entirely new set of App Store Server endpoints.
These server endpoints make it easy to retrieve transaction information and check subscription status on your server.
We also released Version 2 of App Store Server Notifications, to make tracking the subscription lifecycle on your server easier than ever.
Today I'll be going over these new APIs, as well as enhancements we're bringing to the new StoreKit models.
Then, Ian will walk you through some exciting new server updates, including App Store Server API enhancements and brand-new APIs for App Store Server Notifications.
First, I'll go over the new App Transaction API for verifying the purchase of your app.
Next, I'll dig into some new properties we've added to our StoreKit models.
I'll introduce you to the new SwiftUI friendly APIs for redeeming subscription offer codes and asking a customer to review your app.
Then, I'll introduce you to StoreKit Messages, an API used to display App Store messages to your customers.
And finally, I'll go over an enhancement we're adding to preserve Application Username when you're migrating from the original to the modern StoreKit APIs.
Throughout this presentation, I'll be using my favorite app, Food Truck.
In the Food Truck app, I manage a pop-up donut food truck that visits various cities to make donut deliveries.
So, let's get started! Meet App Transaction.
The App Transaction is our new API for verifying the purchase of your app.
The App Transaction represents the signed information for the purchase of your app for the device it's running on.
It's signed using JWS, and it replaces the app detail portion of the app receipt from the original StoreKit API.
Just like transaction verification, StoreKit performs automatic verification of the app transaction for you.
However, if you wish, you can also perform your own validation.
Validating a JWS signature is a well-documented standard.
You can refer to the public documentation to implement your own validation.
StoreKit takes care of automatically updating the App Transaction when necessary.
However, in the rare case that the user thinks there's something wrong, it can be refreshed.
You should provide UI in your app to allow your customers to refresh the app transaction.
This should only be used in response to user action, as refreshing the App Transaction prompts the user to authenticate.
Preventing fraud isn't the only reason to love App Transaction.
If you're looking to switch business models from a paid app to a free app that offers in-app purchases, if you're curious about which of your customers preordered your app, or even if you just want to know when your app was purchased, these are all situations you can handle with App Transaction.
In the app receipt, the receipt payload combine the purchase data about your application along with all the in-app purchases that have occurred.
These are now broken up into two separate components in StoreKit.
The first of these is the Transaction History.
StoreKit's transaction APIs give you insight into the user's entire in-app purchase history, right on the device.
These APIs allow you to find the exact information you need, including the user's latest transactions, unfinished transactions, and current entitlements.
If you prefer to perform these calculations on your server, you can also get the user's purchase history from the App Store Server API.
Ian will have some exciting updates on this later this session.
And the second component is the App Transaction, which contains the data you need to make sure that your app is valid for the device it's running on.
It's easy to verify the purchase of your app using App Transaction, and in just a moment, I'll be going over an example of how you can use it.
But first, let me give you some background about my favorite app.
With Food Truck, I'm able to make donut deliveries, check in on a basic social feed, and visualize my sales history.
Keeping all this information in a database is an ongoing cost for my app, so to help me cover the costs, I'm going to turn the yearly sales history chart into a onetime purchase.
Additionally, I want to enhance the social feed.
So instead of just seeing what others are saying about my food truck, I want to provide the tools so I can engage with my customers as well.
This will be a subscription service, and I'll have a monthly and a yearly plan.
Food Truck began as a paid app, but I'm going to make the switch to a free app that offers in-app purchases.
But I don't want my existing customers who already purchased Food Truck to feel left out.
So, I'll be using the App Transaction to make sure that the customers who purchased Food Truck continue to have access to the premium content they paid for.
Here's the timeline for Food Truck.
At the initial release, Food Truck started out as a paid app that cost $4.99.
Version 1.0 offered donut deliveries, a basic social feed, and sales history charts.
Later, at the release of version 8.0, my business model changed.
Food Truck is now free, but includes a variety of in-app purchases which unlock premium features.
The yearly sales history chart is now a nonconsumable onetime purchase, and now there's a new subscription service for a premium social feed that gives you advanced engagement tools.
Now let's take a look at two different types of customers who might be affected by this.
Alice found out about my Food Truck app in version 2.5, and she decided she wants to share her passion for donuts in the digital world.
So, she purchased my app for $4.99 and began her donut delivery journey.
A second customer, Bob, finds out about my Food Truck app though a friend and downloads it for free in the App Store in version 8.2.
In this scenario, Alice, who purchased my app before it became free, should still have access to all the premium content she already paid for.
She still has the option to purchase the premium social feed subscription, but I don't want to deny her the yearly sales history chart that was initially included.
Bob, however, got my app for free.
I know then not to unlock features and content until they complete the in-app purchase.
So, let's see how we can achieve this with App Transaction in code.
I'll start off by fetching the app transaction by calling AppTransaction.shared.
This call gets me a VerificationResult containing my app transaction.
Within the result, the AppTransaction type contains the JWS payload.
Next, I'll switch on the result.
If the result is unverified, this would be a good time to alert the user that their app purchase could not be verified by the App Store, and then, I can prompt them to refresh the app transaction.
At this time, I'll offer a minimal experience for my app.
If the result is verified, I'm going to use this as an opportunity to check whether the user has purchased my app.
Customers that purchased my app should be granted the services they paid for.
For this, I'll use the original app version property.
This property lets me know the app version in which the customer had downloaded my app for the very first time.
Version 8.0 is the version in which my app became free with in-app purchases.
I'm going to pass the customer's original app version to my function which checks if the user purchased my app before version 8.0.
And with that, I can make an informed decision about how I should go about providing premium content to my users.
For customers like Alice, who purchased my app, I'm going to provide the content the user is entitled to that they had at the time of purchase.
In my case, I'm going to unlock the yearly sales history chart for her deliveries.
Also, I want to check any additional in-app purchases they may have made so I can provide that as well.
Otherwise, I can be confident that the user downloaded my app after I switched my business model, like Bob.
This can be a good time to check the user's current entitlements so I can unlock the features and content they paid for.
And with just a few lines of code, I was able to verify the purchase of my app, check whether the user downloaded the paid version of my app, and I can immediately start providing my premium content, whether the customer purchased my app or not.
With App Transaction, you can easily support your customers whether they're early supporters or if they've just recently downloaded your app.
Now I'd like to move on to the new properties we're adding to our StoreKit models.
The first of these properties is the price locale.
The price locale is now included in StoreKit products.
You may already be familiar with price locale from interfacing with our original purchase APIs.
Next, I'll dig into the server environment property.
Now, you can tell the server environment a transaction or renewal info occurred in.
Then, I'll move onto the recent subscription start date property.
You can use this as a tool to make informed decisions for your customers based on their subscription patterns.
And lastly, I'll go over some special considerations for these properties when you're using them with StoreKit Testing in Xcode.
These properties return sentinel values in older operating systems, and I'll explain what this means in just a bit.
The StoreKit APIs were designed with flexibility in mind, so I'm proud to announce that you can take advantage of these new properties on devices as far back as last year's operating systems, even though they did not originally ship with them.
All you'll need to make this happen is to use Xcode 14 to build your app, and you'll have access to these properties in the previous operating systems.
This is possible because the implementation for these properties are compiled into your app, so when your customers update to the new version, they'll be able to get the benefits of these enhancements without needing to update their operating system.
There is one thing to keep in mind when using these properties, though.
These properties will return sentinel values when you're using StoreKit testing in Xcode in these older operating systems.
When I say sentinel values, I'm referring to placeholder values that signal that these are not real values you should work with, and I'll explain why this occurs.
The sandbox and production environments make use of these properties by extracting the values from the App Store server response.
StoreKit testing in Xcode, however, is a local testing environment that operates independently from the App Store server.
This means we're not able to backport the value of these properties to the previous operating systems there.
You can easily get around this limitation by updating your test device to a new operating system, and you'll be all set to test these values in the local environment.
Let's discuss some situations that demonstrate how you can start using these new properties, the first of which is price locale.
StoreKit products already have a display price property to label the purchase price, but with price locale, you can format numbers deriving from the product's decimal price.
If you have a yearly subscription, you might use this as an opportunity to show your customers how much it would cost them per month.
In this example, you can see that the yearly subscription amounts to $4.17 per month.
Or perhaps you'd want to show them how much they would save if they purchased your yearly service over your monthly service.
With this information, your customers can make informed decisions when they're considering your purchase options.
Now, let's move on to the environment property.
The environment property is available in the Transaction and renewal Info.
This property tells you the server environment in which the transaction or renewal info originated in, which could be Xcode, sandbox, or production.
Your app may communicate transaction information to your server after a customer makes a purchase for bookkeeping and analytics.
When your app generates these transactions, it could be from any one of these server environments.
Like most of you, I don't want to add noise to my analytics with irrelevant test data.
So, knowing the environment can help you filter out unnecessary information from being sent up to your server.
Finally, let's take a look at the recent subscription start date.
The recent subscription start date is available within a product's subscription information, and it represents the most recent period of continuous subscription.
A subscription is considered continuous if there is no more than a 60-day gap between any two subscribed periods.
Keep in mind that this period can contain gaps where the customer was not subscribed to your product, so don't use this as an indicator for the number of days a customer has been subscribed.
The recent subscription start date can help you determine a pattern of loyalty between you and your customers.
For your loyal customers, you might offer them a reward as a way to keep them engaged with your product.
Or if you notice that a customer has unsubscribed from your service, you can use it as a chance to win back a lapsed customer by offering them an incentive to start using your product again.
I mentioned earlier that we'd take a closer look at the sentinel values for these properties.
As a reminder, when I say sentinel values, I'm referring to placeholder values that serve as an indicator of the absence of a real value.
The sentinel values for these properties are easy to identify.
When you're dealing with price locale, the sentinel value is a locale with the identifier xx_XX.
For the environment property, it'll be an empty string.
And finally, for the recent subscription start date, this value is Date.distantPast.
Luckily, the occurrence of these sentinel values are predictable -- you'll only encounter them if you're using StoreKit testing in Xcode in older operating systems, and you can get around this by updating your test device.
So now you've seen the enhancements we've made to our StoreKit models.
And my favorite part is, they're backward compatible all the way back to the operating system in which the models were introduced, so your customers can see the benefits right away just by updating your app.
When you perform arithmetic with price values, the price locale helps you correctly format it so that it matches the App Store's locale.
For transactions and subscription information, the environment tells you exactly where they originated from, so if you store this data on your server, you can act on it accordingly depending on the environment.
The recent subscription start date helps you understand customer loyalty, so you can tailor specific offers to long-time customers, or maybe you can provide an incentive for customers who have unsubscribed.
And in case you were wondering, yes, the environment and recent subscription start date are also available via App Store Server API and App Store Server Notifications, which Ian will discuss.
Now I'd like talk about the new SwiftUI APIs we're providing for redeeming offer codes and requesting a review.
Offer codes can help you acquire, retain, and win back subscribers by providing subscriptions at a discount or free for a limited time.
Now in App Store Connect, you can create uniquely named custom codes.
There, you can set a maximum redemption limit and you can choose whether or not to set an expiration.
Let's look at the SwiftUI implementation to present an offer code redemption sheet straight from your app.
Here, I've got a SwiftUI view with a button to trigger the offer code redeem sheet.
The offer code redemption sheet now has its own view modifier in SwiftUI.
The view modifier is easy to use, it just needs a binding Boolean to start the process.
And once the offer code sheet is dismissed, you'll get a result representing whether or not the sheet presented successfully.
When a customer redeems an offer code for your app, the resulting transaction is sent to the transaction listener.
So, be sure to set up a transaction listener as soon as your app launches to receive new and updated transactions while your app is running.
The offer code view modifier is available starting on iOS 16.
Next, I'd like to talk about updates to request review.
Getting customer feedback is important.
Potential new customers might use reviews as a deciding factor in their decision to download your app.
Others might want to leave a review as a way to provide feedback or suggestions.
Either way, we want to give you the tools to make it easy for you to request a rating from your customers, so you can let them know you're listening and you can continue engaging with them.
Let's review the code.
Here I have a very simple view to demonstrate the Request Review APIs.
In SwiftUI, there's now an environment value called requestReview.
You can use this value to get an instance of the RequestReviewAction, and when you're ready to request a rating, simply call the instance as a function to request to display the review prompt.
You can decide the right time to request a review for your app.
However, you should be aware that the prompt will only be displayed to customers a maximum of three times within a 365-day period.
And you shouldn't ask customers to review the same version of your app multiple times.
Avoid interrupting customers with a review prompt.
A good time to ask for a review could be after they've had a positive interaction, such as completing a purchase on an e-commerce app, or completing a level in a game.
Finally, customers can disable requests from ever appearing on their device, so you shouldn't request a review as a result of a user action.
These APIs are really going to come in handy for your SwiftUI apps.
Next, I'd like to introduce you to our new API for StoreKit messages.
A StoreKit message represents a sheet that appears over your app to display important information to the user.
Messages are vended by the App Store.
Each message has a reason, which is included in the message metadata.
StoreKit messages are retrieved when your app comes to the foreground.
As an example, let's take a look at one of the message reasons -- price increase consent.
When you increase the price of a subscription and it requires user consent, the App Store will inform affected subscribers through email, push notification, and an in-app price consent sheet.
In this case, the App Store requires that the user agrees to the new price of your subscription before it renews at the higher price.
So, if you decide to charge more for your subscription, a price increase consent sheet may appear when a user opens your app if they haven't already responded to your price increase.
By default, StoreKit messages appear over your app when the user brings your app to the foreground, and it may ask the user to take some action relating to your app.
Let's review this.
The entire process starts with your app.
When your app enters the foreground, StoreKit knows to check if there's pending messages to display.
And if there are, StoreKit checks in with the App Store.
The App Store returns information about the message to StoreKit.
At this time, StoreKit checks whether your app is set up to receive messages.
You can do this by setting up a message listener in your app, which I'll get into shortly.
If your app has set up a message listener, StoreKit sends information about the message to your app.
Now's your chance to decide whether or not it's a good time for your app to display the message, or if you want to defer the presentation for later.
If you don't set up a message listener, StoreKit displays the message right away by presenting the message sheet over your app.
I'll go over how to do this in code.
But before I do that, I'll explain a situation in which it would be useful to control the presentation of an App Store message.
In the Food Truck app, I'm able to customize the donuts I'm delivering to different cities.
If a message gets delivered to my app during this time, it would be confusing to the user if they're suddenly interrupted by a message sheet, so I'm going to be implementing the messages API to make sure this doesn't happen by controlling when incoming messages are presented.
Now let's get into the code.
Here, I have a simple view for the donut editor.
As I mentioned earlier, pending messages are sent each time your app comes to the foreground.
So, I want to set up a message listener in each view in which I want to defer the presentation of a message.
I'll add a binding array to collect all the messages that are delivered to my app while I'm in the editing view.
This is important, because if I don't set up a message listener, StoreKit is going to display the message sheet right away when my app comes to the foreground.
As soon as the view appears, I set up my message listener.
I'll do this by setting up a task that iterates over a static property on the message type.
This property is an async sequence, and I'm able to receive messages as they come in.
For my use case, I'm going to save the message in the pendingMessages array.
Since pending messages get delivered each time your app enters the foreground, your app could receive the same message more than once, so I have this condition to avoid adding duplicate messages to my array.
Then, once the view dismisses, I'll display the messages in the parent view.
This is the parent view which holds a navigation link to the donut editor.
Here, I've collected all the pending messages I need to display in this pendingMessages array.
So how do I display these pending messages? Well, now there's an environment value displayStoreKitMessage.
This gets you an instance of a DisplayMessageAction, which you can then use to display a given message.
When the view appears, I'll iterate through the pending messages and call displayStoreKitMessage passing in the message I want to display.
StoreKit takes care of presenting the message sheet.
Earlier, I mentioned that the same message may get delivered to your app more than once.
That's because a message doesn't get marked as read until it's presented to the user.
So, StoreKit makes sure that each unique message is only presented once.
And that was a quick implementation of the Messages API.
Remember, StoreKit messages are sent to your app each time it comes to the foreground, so you'll want to set up a message listener in each view in which you want to control the timing of when the messages are presented.
You can ensure customers have a great experience by making sure message sheets don't appear at unexpected moments.
Or perhaps you want to tailor your logic for certain messages types.
With a price increase consent message, you may want to educate your customer about the additional value you're providing before the price increase consent sheet appears.
Finally, let's review how StoreKit preserves the applicationUsername as an appAccountToken after a user makes a purchase.
If you have a user account system on your server, chances are you're already making use of the applicationUsername property.
The applicationUsername is a string that you create to associate a transaction with a user account on your service.
In the original API for in-app purchase, you set the applicationUsername value when you add a payment to the payment queue.
Although the applicationUsername accepts any string, we recommend that you provide the string representation of a UUID.
When you provide it a UUID string, StoreKit persists the value and you'll see it in the transaction that the queue updates.
If you don't provide a UUID string for the applicationUsername, StoreKit may not persist it.
There's no guarantee the value will persist between the time you add the payment transaction to the queue and when the queue updates the transaction.
When you provide the string representation of a UUID, you can identify which of your app's user accounts began and completed a transaction.
In the modern StoreKit APIs, we implement this concept as a purchase option called appAccountToken and it requires a UUID format.
Now, when you set the applicationUsername to a UUID string during payment, the App Store server stores it as an appAccountToken.
So you'll see its UUID appear in the signed transaction info returned by the App Store Server API and in V2 App Store Server Notifications.
And as a UUID, it's compatible with the appAccountToken in the modern StoreKit transaction APIs.
So, now you can be sure that when you update your codebase to the modern StoreKit APIs, the UUID you used for the applicationUsername is preserved as an appAccountToken in the StoreKit transactions.
We touched on a lot of things today.
Before moving on to the server updates, let's review this year's StoreKit updates.
We discussed validating your app's purchase with App Transaction, redeeming an offer code and requesting a review in SwiftUI, and controlling the presentation of StoreKit messages.
We talked about new price locale, environment, and recent subscription start date properties.
And, we went over the importance of using a string representation of a UUID for the applicationUsername to persist it as an app account token.
I highly recommend you check out our other session "What's new in StoreKit testing." And if you need a refresher on the StoreKit 2 APIs, check out last year's session "Meet StoreKit 2." Now I'd like to hand it over to Ian to walk you through the updates to the App Store server.
Ian Zanger: Thanks, Dani.
Hi, everyone. My name is Ian, and I'm an engineer on the App Store Server team.
Now that you've heard the latest about in-app purchase with StoreKit, I'm going to switch gears and talk about the server.
First, I'll review some recent developments from the past year before moving on to some exciting new updates coming to the App Store Server API and App Store Server Notifications Version 2.
Let's get started.
Last year was big.
We brought you an entirely new suite of endpoints with the App Store Server API and App Store Server Notifications V2, including full sandbox testing support for all these new features.
We shared how you can use the Get Transaction History endpoint to get the full history of a user's in-app purchases, or the Get All Subscription Statuses endpoint to stay up to date with the current state of a user's subscriptions.
Both of these endpoints conveniently key off of a user's originalTransactionId, so you can access this trove of data by storing just this one simple value.
We also covered how version 2 of App Store Server Notifications can simplify event processing on your server and complement the App Store Server API.
With V2 notifications, the App Store server calls your server directly, providing in-app purchase updates as they happen.
The streamlined notification type and subtype make it easy to understand what's happening.
You can use these to track changes related to in-app subscriptions and other events.
With all of these data sources, we wanted to make that data as easy as possible to parse.
Receipts are now a thing of the past, as these new services provide in-app data in signed JSON format, so you can easily parse it and trust that it came from the App Store server.
Last year was a big year for the App Store server.
It may have been big for you as well if you worked to update your server code to leverage all these new features.
Rest assured that effort will continue to pay off as we bring powerful new enhancements and features to App Store Server API and App Store Server Notifications V2.
That's our year in review, but if you'd like more of a refresher after hearing this year's updates, be sure to check out the WWDC21 sessions titled "Manage in-app purchases on your server," "Meet StoreKit 2," and "Support customers and handle refunds." Now let's move on to brand-new updates coming to the App Store server for WWDC22.
First I'll share some updates to transaction and renewal info fields.
Next I'll tell you about new enhancements to the App Store Server API.
And finally, I'll share exciting new features coming to App Store Server Notifications V2.
Now let's dive in with the first of our new topics: new fields found in transaction and renewal info.
Earlier, you heard from Dani about a couple new fields coming to the transaction and renewal info of in-app purchases.
These fields, environment and recentSubscriptionStartDate, are also coming to the transaction and renewal info payloads you receive from the App Store Server API and in V2 App Store Server Notifications.
Let's take a fresh look at the data you can expect to receive from the App Store server with these new fields included.
First is the transaction info payload, which we can see here after decoding.
Down at the bottom, you can see our new field: environment.
You can use it to tell, at a glance, whether the transaction took place in the production or sandbox environment.
Next is the renewal info payload, also seen here after decoding.
As you can see, the environment field is also available here for your reference.
Additionally, recentSubscriptionStartDate will now appear in every renewal info payload.
This is the start date of the user's first subscription purchase in their most recent string of renewals, ignoring any gaps of 60 days or fewer.
recentSubscriptionStartDate is an easy way to get an idea of a customer's loyalty at a glance.
But if you'd like more detail, including the timing and length of any gaps in service, you can call the Get Transaction History endpoint and examine the full history of a user's subscription renewal purchases.
Or for even more detail, with App Store Server Notifications V2, the App Store server automatically sends updates about user subscriptions to your server.
These notifications give you maximum insight into the timing of events like renewal preference changes, offer redemptions, billing failures, and more.
As you can see, recentSubscriptionStartDate rounds out a suite of options for determining customer loyalty.
Use these tools to target offers and reward your most loyal customers.
Now let's move on to some convenient new enhancements to the Get Transaction History endpoint.
With the Get Transaction History endpoint, you can fetch the full history of a user's purchases in your app.
The endpoint response is paginated so you can process this data in reasonable chunks.
Each response contains a revision token that you provide in the next request in order to get the next page.
And the pages are sorted by modified date, meaning each subsequent page contains transactions that are more recently modified.
Let's take a look at how this works.
You call the Get Transaction History endpoint, and provide an originalTransactionId.
The App Store server will return up to 20 signed transactions for that user.
It will also return an updated revision value that you will provide in your next page request for this user.
You'll know there's more data available when the hasMore field in the response is true.
Let's say in this case that there's another page of data available.
You make another request to the endpoint, and you include that revision value from the first response.
You receive the next page of data, including an updated revision value.
hasMore is now false, so you know you're up to date with the latest transaction data.
Except this time, you notice something about the final transaction in the response; you've seen it before! It was one of the original 20 you received in response to your first request.
This means the transaction must have been modified, so it was put back at the top of the sort order.
Now, you can examine the data of that transaction and take note of what's changed.
In this instance, you notice the revocationDate and revocationReason fields are now populated, meaning the transaction was revoked.
You can take action by revoking any content associated with the purchase.
It's a good idea to store the revision value from this final response alongside the originalTransactionId you used to identify the user.
The next time you call the endpoint for this user, you can provide that revision and know that you're getting back only fresh transaction data that has been modified since your last request.
As you've seen, the Get Transaction History endpoint provides you a simple way to retrieve a comprehensive set of in-app purchase data.
But maybe sometimes it can be a bit too comprehensive.
Some users have lengthy purchase histories dating back several years.
For these users, this endpoint could return hundreds of purchases of a variety of types.
Even with pages, this can be a lot to handle.
That's why this year, we're enhancing this endpoint with a variety of new sort and filter options.
Now, you can tell us exactly the data you want from the start, saving processing time on your server and reducing the number of network calls needed to retrieve all available pages.
You can sort by descending modified date if you're interested in seeing the most recently modified purchases on the first page of results.
You can also filter by several useful fields such as product type, product ID, Family sharing status, and more.
To apply these new sort and filter options, just append them as query parameters to your request to the Get Transaction History endpoint.
Let's take a closer look at how that works.
Here you can see all the new parameter options.
These may look familiar, since most are taken directly from the transaction info payload.
You can mix and match these parameters to get very specific results.
For example, maybe we want to fetch only the nonconsumable purchases a user has made since the beginning of this year.
We also want to exclude any revoked purchases.
We will build our custom request by setting the productType to NON_CONSUMABLE and specifying the startDate as the beginning of this year represented in milliseconds.
Finally, we'll set excludeRevoked to true.
And that's our request! Since we did not specify a sort order, the response will default to sorting by ascending modified date.
Now even with a request as specific as this, there could be multiple pages of purchases to retrieve.
For follow-up requests, we should make sure to include the exact same query parameters, in addition to the revision from the previous response.
For even more flexibility, three of the filter fields support multiple values, so you can filter to only those purchases that match at least one of the provided values.
These fields are productType, productId, and subscriptionGroupIdentifier.
To provide multiple values for these parameters, simply define them multiple times.
Next let's move on to App Store Server Notification updates.
With App Store Server Notifications V2, you can take your server to the next level.
V2 notifications give detailed insights about in-app purchase events that you can't get anywhere else.
These are especially useful for tracking the lifecycle of autorenewable subscriptions offered in your app.
You can use these insights to retain customers, win back those that have churned, resolve customer support requests, and more.
With all of these benefits, you might wonder how to get started.
As with any new feature, the sandbox testing environment is the best place to start.
That's why last year, we added the ability to set a separate server URL in App Store Connect for receiving App Store Server Notifications in sandbox.
After registering your server URL, you'll want to confirm your server is receiving notifications from the App Store server.
You might set up a sandbox account just to trigger a notification through a user action.
For example, let's say you perform a first time buy of a subscription using that sandbox account.
You should receive a V2 notification of type SUBSCRIBED and subtype INITIAL_BUY.
But what if that notification doesn't come? You might wonder if there was an issue with your server or the steps you took to trigger a notification.
This situation can generate a lot of uncertainty right as you're getting started.
We want to simplify this experience and give you a way to easily verify that App Store Server Notifications can reach your server.
That's why this year, we're introducing the new Request a Test Notification endpoint.
By calling this simple endpoint, you can ask us to send a V2 Notification of type TEST to the server URL registered for your app in App Store Connect.
The new TEST notification type is used exclusively for this endpoint.
You can call the endpoint in sandbox or production to test your saved URL for either environment.
Use this new endpoint to quickly test new server URLs and configurations.
Let's see how this simplifies first-time setup.
Now, if you're just looking to trigger your first notification, there's no need to set up a sandbox account or perform a purchase.
Just call the new endpoint in whichever environment you want to test and you'll receive an HTTP 200 response confirming your request.
The response will contain a new field, testNotificationToken, which identifies the test notification your server will receive.
We will come back to this field later.
Shortly afterward, your server should receive a V2 notification of type TEST at the URL saved in App Store Connect.
Now let's see how to call this endpoint.
Just send a simple POST request to this new path on the App Store server.
You'll receive an HTTP 200 response and know that your request has been submitted.
The response will contain that new field I mentioned, testNotificationToken.
Take note of this for later.
Soon you'll receive a signed TEST Notification.
Here's what that notification will look like once it's decoded.
You'll notice it contains all the usual top-level fields of a V2 notification, including the new notificationType, TEST.
The contents of the data object are a bit shorter than a normal notification.
Since this is just a test, there are no transaction-related data to include, so we omit transaction-specific fields, most notably the signedTransactionInfo.
When calling the new Request a Test Notification endpoint, keep in mind that App Store Server Notifications are sent asynchronously.
Your successful call to the endpoint will return an HTTP 200 but the actual test notification will arrive separately, a short while later.
Given that this endpoint is all about testing your server configuration, you might be wondering what to do when that test fails.
In other words, what if the test notification doesn't arrive? To further enhance your testing capabilities, we're releasing the Get Test Notification Status endpoint, which you will use in conjunction with the Request a Test Notification endpoint.
With this new endpoint, you can check on the status of a previously requested TEST notification.
The endpoint response will tell you if the App Store server was able to reach your server and successfully send the TEST notification.
If the send failed, it will give you an idea of why, so you can better troubleshoot your server configuration.
Let's check out how you'll use this endpoint.
Send a GET request to this path on the App Store server.
In the path, include the testNotificationToken you received from the Request a Test Notification endpoint.
This will tell us which test notification you want to check the status of.
Now for the response.
The signedPayload field contains the TEST notification payload that the App Store server attempted to send to your server.
And the firstSendAttemptResult field indicates the result of that send attempt.
Here, SUCCESS indicates that the send was successful, meaning the App Store server received an HTTP 200 response from your server.
If the send was unsuccessful, you'll instead see one of several different error values.
These values indicate the error the App Store server experienced trying to reach your server with the test notification.
With this information, you can troubleshoot your server issue, request new test notifications as needed, and get your server running reliably.
Collectively, these test notification endpoints are simple to use and can save you a lot of trouble when setting up or reconfiguring your server to receive V2 App Store Server Notifications.
Now with the help of these endpoints, you can set up your server and confirm it's running smoothly.
But servers aren't perfect and outages happen.
How do you recover when your server goes down, leading you to miss App Store Server Notifications? The current solution to this is a retry system.
When the App Store server fails to reach your server, it initiates a retry process.
It will retry sending the same notification up to five times, with a longer wait between each attempt.
These retries take place only in the production environment.
Retries help you eventually recover from an outage, but they're not perfect for every situation.
For example, some outages can be extensive.
If your server is down long enough to miss the final retry attempt from the App Store server, that notification is lost.
Or more commonly, your server could experience a very brief issue, during which it misses only a handful of notifications.
But missing even a single notification means some of your customer records are out of date for at least an hour.
Yet you don't know which ones! Clearly, server outages are stressful, and recovering from them can be a complex task.
That's why we want to make it as easy as possible to recover missed App Store Server Notifications, so you can get your server back on track as soon as possible.
That's why this year, we are introducing the new Get Notification History endpoint.
With this endpoint, you can fetch the history of V2 App Store Server Notifications generated for your app.
Whether your server successfully received a notification or not, that notification will appear in the response of this endpoint.
When calling this endpoint, you'll specify a date range of notifications to fetch.
With WWDC, we have started recording this data, and we will build up to the cap of the latest six months of rolling history being available.
You can optionally filter your request by type and subtype, or fetch just a single user's notifications by providing an originalTransactionId.
And the existing retry system is still available, so you can use it in tandem with this new endpoint.
Let's take a look at how you'll call this endpoint.
You'll send a POST request to this new path on the App Store server.
In the request body, you'll include a startDate and endDate.
The response will contain only notifications we first attempted to send in this window.
Keep in mind that the earliest notifications available will be those sent six months before the date of your request.
Optionally, you can specify a notificationType and notificationSubtype.
If you do, the history will be filtered to only notifications that match both of these values.
Keep in mind that some notifications have no subtype.
Alternatively, you can provide an originalTransactionId of a user, to fetch the notification history of only that user.
Finally, you should provide a paginationToken as a query parameter for every follow-up request in order to get the next page.
Make sure you use the same request body for follow-up requests, changing only this paginationToken.
Now let's take a look at the response.
The notificationHistory array contains up to 20 notifications, with the oldest notifications first.
Each entry in this array represents a notification and inside you'll find the signedPayload, which you can decode as usual to view the transaction data.
The data within is identical to the payload the App Store server sent in the original notification.
You'll see that we've also brought the new firstSendAttemptResult field to this endpoint response.
You can use this field to look for sequences of timeouts and other errors to better understand why your server missed notifications in the past.
The response also contains a paginationToken if there are more pages to retrieve.
You should provide this in your next request in order to get the next page of notifications.
You'll know there are more pages to retrieve as long as the hasMore field is true.
And that's everything you need to know about this useful new endpoint.
That concludes our App Store server updates for today.
Every server feature announced today is available now in both sandbox and production.
We hope you'll take advantage of these new features to make your server the best it can be.
For more great content on using a server with in-app purchase, including how to use the latest features while supporting legacy clients, I encourage you to check out another session at WWDC22, "Explore in-app purchase integration and migration." Both: Thanks for joining us at WWDC22! ♪
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.