Streaming is available in most browsers,
and in the Developer app.
-
Explore App Store server APIs for In-App Purchase
Learn how to leverage your server to build great In-App Purchase experiences with the latest updates to the App Store Server API, App Store Server Notifications, and the open source App Store Server Library. After a recap of current APIs, we'll introduce updated endpoint functionality, new transaction fields, and a new notification type. We'll also discuss best practices for the purchase lifecycle, delivering content, and targeting offers, so you can become a server power user.
Chapters
- 0:00 - Introduction
- 4:52 - Purchase lifecycle
- 13:51 - Delivering content
- 20:55 - Subscriptions and Offers
Resources
- App Store Server API
- App Store Server Notifications
- consumptionRequestReason
- currency
- Forum: App Store Distribution & Marketing
- Get Transaction History
- offerDiscountType
- price
- refundPreference
- renewalPrice
- Send Consumption Information
- Simplifying your implementation by using the App Store Server Library
- Submit feedback
Related Videos
WWDC24
WWDC23
WWDC22
-
Download
Hello and welcome to "Explore App Store server APIs for In-App Purchase". I'm Alex, an engineer on the App Store server team.
And I'm Ian, also an engineer on the App Store server team. In this session we'll cover the server APIs for in-app purchase available to you on the App Store. I'll take you through some use cases for the server APIs, showing you how to go above and beyond what's possible using on-device code alone. For my portion of the session, I'll detail new features coming to the App Store server APIs. Whether you already have a server or are just getting started, there's something to be excited about. There's a lot to cover, so let's get started.
As an app developer, you're likely familiar with App Store Connect, which you use to configure your app's settings and products. You're likely also familiar with StoreKit, which you use to add in-app purchase functionality inside your app. These two technologies are essential, but there's a third which you can leverage to take your in-app purchases to the next level: the App Store server.
The App Store server has three important pieces starting with the App Store server API.
The App Store server API allows you to make requests from your server to the App Store server.
It allows you to query for information about transactions. It also allows you to submit information relating to those transactions, for example extending a subscription's renewal date.
Four of the endpoints in the App Store server API allow you to obtain information about transactions, for example getting a customer’s transaction history. An additional five endpoints are related to refunds and customer satisfaction, for example you can send consumption information to participate in the refund decisioning process.
The last three endpoints are for App Store Server Notifications. You can trigger a test notification and see your notification history with these endpoints.
Altogether, these twelve endpoints of the App Store server API replace and go beyond what was possible with the Verify Receipt endpoint which was deprecated in 2023. Now that I've covered the endpoints available for you to call the App Store. App Store Server Notifications allow the App Store to call your server. App Store Server Notifications allow the App Store to proactively inform your server of transaction updates.
The App Store can inform your server of events like upgrades or renewals within the subscription lifecycle.
Notifications also cover the refund lifecycle, such as letting you know when a refund has occurred.
Now, the App Store Server Library. The App Store Server Library is the foundation for using the App Store server API and App Store Server Notifications. The App Store Server Library is designed to simplify your integration with the App Store server.
It provides a client for the App Store server API, making it easier than ever to get started using these endpoints.
It has built-in signed data verification, allowing you to validate and decode data from the device, the App Store server API, or App Store Server Notifications.
The library also allows extracting transaction information from deprecated receipts, providing a transition path away from the deprecated Verify Receipt endpoint and Original StoreKit client framework.
Additionally, it offers a simple way to create promotional offer signatures.
Last year we were excited to release the production-ready version 1.0 in four languages: Java, Python, Node.js, and Swift. The App Store Server Library is now the recommended way to use the App Store server APIs in these languages. To get started, you can download the library via the appropriate package manager for each language.
Another great feature of the library is that it is open source! We encourage you to submit feedback and open pull requests. Join us on GitHub to get started, and consider becoming part of the growing community of developers contributing to the library.
For more information about the use and setup of the App Store Server Library, please see our WWDC23 session, "Meet the App Store Server Library".
Now that I've covered the foundation of the App Store server, I'll move on to the three core topics of today's session. For each topic I'll share some best practices, and then Ian will share what's new. First, I’ll cover the purchase lifecycle and Ian will share what is new with App Store Server Notifications. Next, I’ll discuss delivering content, and how to approach this workflow from a server perspective. Ian will then share some enhancements in this space. Finally, I'll share new developments in the subscriptions and offers space, with Ian covering new fields available for these transactions. Let's get started with the purchase lifecycle.
There are a variety of in-app purchase types; here is a brief overview.
Non-consumables are products that can be purchased once and provide lifetime access. Consumables, as the name suggests, can be used up and repurchased by the customer as desired. An example of a consumable is a pack of 100 gems, which can be used to purchase in-game items.
Non-renewing subscriptions are similar to consumables. These can be repurchased by the customer as their period ends.
Auto-renewable subscriptions are your standard subscriptions, which renew on a schedule. They are also re-purchasable in the case of renewals or churn. That’s a recap of the in-app purchase types. Now to learn more about the purchase lifecycle, I'll take you through an example of a consumable purchase. The workflow begins when the customer purchases a consumable in your app.
The customer’s device receives a signed transaction that your app can then send to your server to validate the transaction and dispense the content.
Additionally, the App Store server sends a ONE_TIME_CHARGE notification to your server with the same signed transaction. Typically this is where the lifecycle ends for the purchase, with the customer ready to buy another consumable.
But what happens if the customer requests a refund for the purchase? At this point, the App Store server may send your server a CONSUMPTION_REQUEST notification, requesting that you provide information about the customer’s use of the product.
You can provide that information by calling the Send Consumption Information endpoint. The information you submit will be considered in Apple's refund decisioning process.
Once Apple grants or denies the refund, you'll receive either a REFUND or REFUND_DECLINED notification informing your server of the results.
The latter part of this workflow might look a little intimidating at first, but the App Store Server Library can help.
Here's one way to implement the consumption request workflow with the App Store Server Library. I’m using Java in this example, but the library is also available for Node, Python, and Swift on Server as well.
First, I create a SignedDataVerifier and AppStoreServerAPIClient. For brevity I omit the arguments here.
Next, let's assume I recently received a notification and have the signed payload stored as a string in the signedNotification variable. As with any notification, I verify and decode the payload using the SignedDataVerifier, and store the decoded object in the notification variable.
Now, in this case I check if the NotificationType equals CONSUMPTION_REQUEST, and if it does, I proceed with the appropriate logic for that type.
I take the signedTransactionInfo from the data object of the notification and store it in a String. Then I extract and verify the signed transaction using the SignedDataVerifier, and store the transactionId in a variable. Next, I'll build a consumptionRequest object to send to the endpoint.
How you determine the appropriate values for this object will vary depending on your server implementation and the transaction at hand.
I've included a couple example values here, indicating sample content was provided to the customer and the content was consumed on an Apple platform.
Once I have constructed the consumptionRequest object, I then call the Send Consumption Information endpoint using the apiClient created earlier. I pass in the transactionId and consumptionRequest. If no exception is thrown, the data was successfully submitted. And that's how to handle the consumption request workflow using the App Store Server Library. Now Ian, you have some updates about what's new in this space? I sure do. We have some great new features to improve your handling of purchases and refunds.
First up is the one-time charge notification which Alex included in his consumable workflow example. This brand new notification type is sent for one-time purchases of in-app products. Now when someone purchases a consumable, non-consumable, or non-renewing subscription in your app, your server will receive a notification.
This is in addition to the existing notification types that we send for new purchases and renewals of auto-renewable subscriptions.
By listening for these types together with one-time charge your server will stay up to date with every single purchase customers make in your app.
This new notification is available today in sandbox for you to start testing.
It will be available in production later this year.
Here's an example of a decoded ONE_TIME_CHARGE notification.
The notificationType is ONE_TIME_CHARGE.
The decoded transaction info contains all the relevant data for the in-app purchase.
In this example, the customer purchased a consumable pack of 100 gems.
If you provided an appAccountToken at the time of purchase, you can find it here.
You can use this value to understand which customer account on your server made the purchase. This allows you to immediately unlock the content for this purchase using just the data provided in this notification. Now there's no need to call the App Store server API or wait for a call from the device. One important part of the purchase lifecycle is refunds. As Alex mentioned, the CONSUMPTION_REQUEST notification is sent today when someone requests a refund for a consumable in-app purchase. You can respond by calling the Send Consumption Information endpoint, as Alex mentioned.
But what if you offer auto-renewable subscriptions in your app? We want you to be more involved in the refund decisioning process, so now we also send CONSUMPTION_REQUEST notifications for refund requests submitted for auto-renewable subscriptions.
Additionally, a new field called ConsumptionRequestReason is now included in all CONSUMPTION_REQUEST notifications. This field indicates the customer's stated reason for requesting a refund of the in-app purchase.
Here's an example of a CONSUMPTION_REQUEST notification sent for an auto-renewable subscription.
The new field consumptionRequestReason indicates why the customer requested a refund. In this example they made the purchase unintentionally.
We also have updates for the process of responding to CONSUMPTION_REQUEST notifications. When you call Send Consumption Information, you provide contextual information about the purchaser's use of the product. But we want you to take a more active role in the decisioning process if you choose.
Accordingly, you can now submit a preference for granting or denying the refund when calling Send Consumption Information.
Use the new consumptionRequestReason field from the CONSUMPTION_REQUEST notification to inform this preference. Your preference and any other consumption data submitted will be taken into account in the final decisioning process. Now, building on Alex's code, I'll show how you can use these new features with the App Store Server Library. The code for decoding the notification and getting the transaction information is unchanged. But now the notification data contains the new ConsumptionRequestReason.
If you'd like to express a preference for accepting or denying the refund, you can determine that preference according to your own logic. As an example I've included a call to a custom method here, called determineRefundPreference. In such a method, you may wish to consider the consumption request reason and the transaction, along with other data.
Finally, set your refundPreference on the ConsumptionRequest object, then send it to the App Store server as before.
And you're done! When using the App Store Server Library, integrating new features takes just a few lines of code.
Sending consumption information is one way you can play a bigger role in the refund process. Your biggest role, however, is to deliver a seamless purchase experience that doesn't generate refund requests in the first place. One way to do that is ensuring people using your app get immediate access to content they purchase. Alex, do you have any advice on that topic? Great question Ian, let me share some best practices for delivering content.
First of all, let's understand what the content delivery workflow looks like with a server. It all starts with the customer purchasing an in-app product. Subsequently, your app sends the signed transaction info to your server. At this point you grant the user access to the product. For example, for a consumable you might update the user's in-game currency balance on your server.
You signal back to your app on the device that the content for the transaction has been granted. Then, your app marks the transaction as finished. Marking the transaction as finished indicates to the App Store that the content has been granted and the customer is ready to make another purchase.
Let’s focus on the content granting step. Here are some best practices for granting content with the server. Because you have sole control over your server, it should be the only source of truth for what your customers have access to. Do not rely on the device to be a source of truth for what a customer in your system owns, as unsigned data could be modified on or between the device and your server. Additionally, since your server is solely responsible for granting content, it should also be the source of truth for what content has been granted. Once your server has granted content for an on-device transaction, your app should mark it finished as a signal to the App Store server. But you should not use the finished status of a transaction as an indicator of content delivery, as this could lead to you granting content multiple times or not at all.
No matter where your server obtained a signed transaction, validate the signature before granting content. As we saw earlier, this is easily done using the App Store Server Library. With your server responsible for granting all content, it's important that it discovers new and updated transactions quickly to provide the best customer experience. There are a variety of ways to make sure your server doesn't miss transactions. Send all new and updated transactions from the device to the server. Enable App Store Server Notifications V2 for your app. All purchases result in a notification, including purchases like renewals which likely happen while the customer is not using the app. This allows you to discover purchases for a customer without relying on the device. Use StoreKit to set an appAccountToken generated by your server for the customer at purchase time. When you receive App Store Server Notifications for the purchase, you can use this value to link the included transaction data to the customer without requiring the device.
Alternatively, if you have reason to believe you missed a purchase, you can use the Get Transaction History endpoint of the App Store server API to get the customer’s history and check for any missed transaction updates.
Here’s an example of how to call the getTransactionHistory endpoint using the App Store Server Library.
Here I have my apiClient as before, and I assume I have a transactionId belonging to the customer. I can use any single transactionId of the customer to fetch their full transaction history.
Next, I'll construct the request. This endpoint supports various filter and sort options. Here I'll just specify I want the transactions listed in ascending order by last modified date. I create a HistoryResponse variable to hold the response from the endpoint, a List to store the signed transactions, and a String to hold the revision. Since this is my first time calling the endpoint for this user, the revision should be null. If I had previously fetched this user's transaction history, I could instead provide the revision from the most recent HistoryResponse in order to fetch only newly updated transactions.
To make the request, I pass in the transactionId, the revision returned by the previous request, if it exists, and the request object. I add all the signed transactions from the response to the output list. I then update the revision variable so I'm ready to get the next set of transactions.
This endpoint is paginated; therefore I'll loop over the code that calls the endpoint until the response has no more transactions, indicated by the HasMore field in the response being false.
Once the loop has finished, I have a list of all the transactions for the customer, which I can then decode with a SignedDataVerifier as we saw earlier. Then I can examine the list to check for any new and updated transactions and take the appropriate action, such as delivering the content.
So that's how you can use Get Transaction History with the App Store Server Library.
That seems great Alex, so you can really fetch every transaction with that endpoint? Well, not exactly. The endpoint only returns consumable transactions that are refunded, revoked, or unfinished on the device. But that's how it's always been, since the endpoint was first introduced. It sounds like it's due for an update. Today we are releasing a new version of Get Transaction History. Version 2 of the endpoint returns all transactions for a given customer, regardless of product type, refund status, or finished status.
This is truly the full history of the customer's transactions, which opens up brand new use cases. For example, you can use it to show customers their full purchase history. Or, perform a refresh of your server-side purchase entries for a single customer.
You can even audit someone's consumable balance on your server to ensure it's fully up to date with all expenditures.
The new version of Get Transaction History returns all the same data as the original version, and more. Accordingly, the original version is now deprecated.
Migrating is simple because the new endpoint is substantially similar to the original version. Just update the version in the URL path to V2, prepare your server to encounter finished consumable transactions, and you're ready to go. Using the new version of Get Transaction History is easy with the App Store Server Library.
If we revisit Alex's code, the only change is in the call to the endpoint.
The new version parameter lets you choose what version of the endpoint to call.
You can populate the list of transactions from the response just as you did before, but keep in mind the signed transactions list now includes all consumables.
So that's the latest on transaction history. But what good is a complete transaction history if it's completely empty? One way to generate lots of transactions is offering auto-renewable subscriptions in your app. Attracting new subscribers and retaining existing ones takes ongoing effort, but it can be well worth it. Alex, maybe you can take us through some options to help with that? Happy to! Next, I'll discuss subscriptions and offers, including how you can use offers to attract and retain customers to your subscription products. I'll start with the three payment modes available for auto-renewable subscriptions.
There are several different payment modes that can be configured when creating offers. One option is to offer customers free trials. Free trials are a great way to encourage customers to give your services a try before having to pay.
Alternatively, you can offer customers a reduced price on a pay as you go model, such as two months half-off.
Finally, you can offer customers a pay up front offer that allows them to prepay a period at a reduced price. Now that we've covered the various payment modes, I will cover the variety of offer types. First, introductory offers. Introductory offers are offers applied to new subscribers to a subscription group. The eligibility and distribution of these offers are handled by Apple, ensuring a customer has not previously redeemed a given offer. Now, offer codes. These are codes that can be distributed to your customers and redeemed for an offer in your app. Apple ensures a code is only redeemed the number of times you specify, while you decide which customers to distribute the code to.
Next, promotional offers. These are offers that can be used to retain existing customers or encourage churned subscribers to return. These are offered on-device and eligibility decisioning is completely controlled by you.
Finally, the new win-back offer type. Win-back offers can be shown to expired customers to win them back.
For more information about this new offer type, please see the WWDC24 session "Implement App Store Offers".
Of all the offer types, promotional offers are the most reliant on server logic, as they require a signature to distribute and the eligibility logic is up to you. Next, I'll take a closer look at how that works.
Here is an example of the promotional offer workflow. Imagine we have an existing customer that has disabled auto-renewal for their subscription, meaning their subscription will soon churn.
The App Store server will inform you of this via a notification with type DID_CHANGE_RENEWAL_STATUS and subtype AUTO_RENEW_DISABLED. Now you have the option to target the customer with a promotional offer to encourage them to keep subscribing.
If you decide the customer should receive a specific offer, your server needs to then create a promotional offer signature to send to the customer's device. Your app provides the signature to StoreKit in order to present the customer with the option to purchase the product at the promotional price.
You will be informed of this redemption via a SUBSCRIBED or OFFER_REDEEMED notification.
Now, one of the more complicated pieces of this workflow is creating the promotional offer signature, or at least it was until we released the App Store Server Library. Let me explain the signature creation process with the App Store Server Library.
Here is the code to create a promotional offer signature.
First, I instantiate a PromotionalOfferSignatureCreator.
I pass in the private key, keyId, and bundleId for my app. These values will be used to sign the promotional offer signature, which prevents customers from redeeming promotional offers without my consent.
Next, I specify the productId and offer identifier for the offer and the app_account_token for the customer. I create a nonce and record the current timestamp. The nonce allows the App Store to prevent the offer from being redeemed multiple times.
I pass these parameters to the signatureCreator and receive a Base64-encoded signature that can then be sent to the customer's device. So that's how to sign promotional offers using the App Store Server Library. Now, you might be wondering how to decide who to make these offers to. Ian, I bet you have some updates to share? I do! A lot of thought can go into promotional offers, including what types of offers to create and who should receive them. We want you to have as much information as possible about subscriptions and offers, so you can make better decisions for your business and your customers.
To that end, in late 2023 we added a handful of new fields to the transaction data provided by our server APIs and on-device via StoreKit. The new price and currency fields indicate the display price and currency of the purchase the customer made, inclusive of any offers applied. When using these fields, remember that App Store Connect reporting is the source of truth for financial and accounting purposes. If an offer applied to the purchase, the new offerDiscountType field indicates the payment mode of that offer, either FREE_TRIAL, PAY_UP_FRONT, or PAY_AS_YOU_GO.
Here's an example of the decoded transaction info for a subscription purchase.
The new price and currency fields show the display price of the product at the time of purchase. The price is in milliunits of the currency. In this example, 4990 indicates a price of four dollars and ninety-nine cents in the currency USD.
We can reference the existing offerIdentifier and offerType fields to understand which offer the user redeemed and its type.
And now the new offerDiscountType field indicates this was a "PAY_UP_FRONT" offer.
These fields are a great reference point to understand the price points of purchases made in your app at different times. Now you can check a customer's transaction history and understand the price they paid, even if the product's price has since changed.
Here’s an example of how the new fields work for a subscription purchase.
If you check the transaction info for the initial period, you'll see this purchase is part of a "PAY_UP_FRONT" offer. The display price for the transaction is 4.99 in the currency USD.
At the end of the two-month promotional period, the subscription renews. The customer is now at the normal price of 9.99 USD.
Our APIs also provide renewal info for each of your customers’ subscriptions, so you can understand what will happen the next time the subscription auto-renews.
I'm happy to share that three new fields are now available in the renewal info as well: renewalPrice, currency, and offerDiscountType. These new fields will help you understand the expected display price and offer payment mode that will apply at the next renewal of a subscription.
Here's an example of the decoded renewal info for a subscription.
The renewalPrice and currency fields indicate the App Store server expects the next renewal purchase to have a display price of 8.99 USD.
The existing offerIdentifier and offerType fields indicate the offer and offer type the App Store server expects will apply to the next renewal purchase.
And now the new offerDiscountType field indicates the App Store server expects the renewal purchase to be part of a "PAY_AS_YOU_GO" offer.
Revisiting our pay up front offer example, we can see how these fields work. During the initial promotional period, the renewal info contains information about the upcoming normal period. You can see the full price of 9.99 USD will apply at renewal.
Following the first renewal, the renewal info reflects what will occur at the second renewal. Since the customer is now at the normal subscription price, the fields are unchanged.
Now I'll cover a "PAY_AS_YOU_GO" example. During the initial promotional period, the renewal info reflects the purchase that will be made at the first renewal.
The offerDiscountType field indicates the next purchase will still be part of the "PAY_AS_YOU_GO" offer.
The renewal price is 2.99 USD, which includes the offer discount.
After the first renewal, the renewal info contains information about the second renewal. The renewalPrice indicates the subscription will return to the normal price of 4.99 USD.
The renewal info is unchanged following the second renewal. Targeted promotional offers are a powerful tool for attracting and retaining customers to your subscription products. With the data available through the App Store server APIs, you can cater your server-side eligibility logic for those promotional offers to suit your specific needs. For example, before advertising an offer to someone, you could check that they haven't redeemed one previously in your app. Or for customers that have only made purchases at lower price points, you could create an offer that lets them try a premium product at a reduced price for a limited time.
I hope you'll find the new fields we've added helpful as you build your offer strategy. Now, I'll briefly review the new features shared in today's session. The new ONE_TIME_CHARGE notification is sent for one-time purchases, so now you can track every single in-app purchase made in your app by using App Store Server Notifications.
CONSUMPTION_REQUEST notifications are now sent for refund requests submitted for auto-renewable subscriptions in addition to consumables. And these notifications now include the new ConsumptionRequestReason field.
Using that field and other server-side data, you can now express a preference for granting or denying the refund when calling the Send Consumption Information endpoint.
The newly updated Get Transaction History endpoint returns transactions for every single purchase a given customer has made in your app, so you can retrieve comprehensive data on-demand.
Finally, new fields like price, currency, and offerDiscountType give you greater insight on subscriptions and transactions.
That's all from me. Alex, any final things to share? Yes! We would love to hear your feedback on the features shared today, and what you'd like to see next from the App Store server. If you have feature requests or feedback for the App Store server, please let us know through Feedback Assistant. A reminder, the App Store Server Library that we used throughout the session today is open source! Join us on GitHub to submit issues and contribute. And for more information on the App Store server, check out these sessions from previous years.
Thanks for joining us!
-
-
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.