Learn how you can migrate to the latest App Store Server APIs and incorporate App Store Server Notifications. We'll help you get started with these tools and provide best practices to make the most of managing in-app purchases on your server. Discover how you can sign JSON Web Tokens, verify signed transactions, and migrate from verifyReceipt.
♪ ♪ Gabriel Ting: Hello, and welcome to our session, "Explore in-app purchase integration and migration." This session is divided into two parts: one dedicated to migrating to the App Store Server API, and one dedicated to migrating to App Store Server Notifications Version 2. My name is Gabriel, and I am discussing how to migrate to the App Store Server API. Alex Baker: My name is Alex, and I will walk you through migrating to App Store Server Notifications Version 2. Gabriel: Let's first begin with a brief introduction of the App Store Server API and App Store Server Notifications. We introduced the App Store Server API last year as a powerful, secure, and efficient way to obtain data and perform operations from your server. We aim to give you just the data that you need, signed in the JSON Web Signature or JWS format, so that you can verify that the data you receive is untampered with, intended for you, and signed by the App Store. For example, one of our App Store Server API endpoints, the Get Transaction History endpoint, in combination with the new filter and sort features, allow you to fetch any set of transactions that you specify, with just an originalTransactionId. On the pane of App Store Server Notifications Version 2, with the increased number of states of a subscription that can be represented, Notifications Version 2 will update you of all possible states of a subscription, in real time. We aim to proactively provide all the information that you need to know about what's going on with your subscribers, without having to ask us for the information. Alex will walk you through this more in his portion of this session. If using these features with ease and efficacy interests you, this session is for you. We will walk you through how to get started using the App Store Server API and App Store Server Notifications Version 2, as well as some migration tips and best practices. For additional information on each of these, please refer to these additional sessions listed below.
So let's get started discussing migrating to the App Store Server API. First off, we will be discussing how to start using the App Store Server API. Second, we will dive into some of the details of signing JSON Web Tokens. Third, we will demonstrate how to verify signed transactions you receive from the App Store are genuine. Finally, we will discuss how to migrate from verifyReceipt to the App Store Server API. Let's get started. First, let's get into talking about how to use the App Store Server API with different versions of StoreKit, first with just original StoreKit, then with just StoreKit 2, then discussing methods to support both simultaneously for both clients using iOS versions that support StoreKit 2, namely those on iOS 15 or greater, and those that do not.
First off, let's take a look at what requests to the App Store Server API look like. We see that the five APIs listed here utilize originalTransactionId as a path parameter. This allows you to easily call these APIs using the originalTransactionIds that you receive from receipts, signed transactions, signed renewals, and notifications. Next is the Look Up Order ID endpoint. This endpoint instead uses the orderId provided by a customer for support queries. This is so that you can better assist with questions directly from the customer, as the customer is provided an orderId in customer receipts for each transaction, but is not provided an originalTransactionId. This ensures that you can directly respond to customer inquiries with data customers have on hand. The last endpoints listed here are notification related, which Alex will touch on in his portion of this session.
Next, let's look at where you can get the originalTransactionIds from in Original StoreKit. When you call verifyReceipt with the unified app receipt, the originalTransactionId you'll use when calling the App Store Server API comes back in the in_app field in the receipts for each of the transactions this user purchased, as well as in the latest_receipt_info and pending_renewal_info. Now that we know how to get the originalTransactionId from Original StoreKit transactions, let's look at the whole flow between a customer, the App Store Server, and your server.
First, obtain the app receipt on your server. Next, take the app receipt and call verifyReceipt with it from your server.
This will return the decoded receipt. From the decoded receipt, gather all of the originalTransactionIds in precisely the same ways I previously showed. Next, you can call the Get Transaction History endpoint with any one of the gathered originalTransactionIds, which will return the history of transactions for this user as signed transactions. These transactions include non-consumables, refunded consumables, non-renewing subscriptions, and auto-renewing subscriptions. Then, if you want to get the latest signed transaction and signed renewal information for a specific subscription, call the Get All Subscription Statuses endpoint with the corresponding originalTransactionId. This will return all of the signed transactions and renewals for the subscription that corresponds to the given originalTransactionId. Next, let's look at where the originalTransactionId is in the case of StoreKit 2 transactions. This is the code on the client to obtain an originalTransactionId from a transaction. On devices that support StoreKit 2– namely devices that are on iOS 15 or later– you can get the originalID property on a verified, decoded transaction to get the originalTransactionId. Now, looking at the server side, here is an example of a signed JWS transaction, which is the data type that you receive in signed transactions and signed renewals from the App Store Server API and App Store Server Notifications. Here, we see that originalTransactionId is a top level field.
Next, let's look at the whole flow between a customer, the App Store Server, and your server for StoreKit 2 transactions. First, take the signed transaction on the device. With StoreKit 2, you can verify this transaction on-device. Using the on-device status listener, transaction listener, or last transaction will keep you updated on latest transactions, cancellations, and refunds, which can then be sent to your server for record-keeping. For example, these are updated on subscription renewals, subscription offer redemptions, expirations, and more.
Send the transaction to your server. In combination with App Store Server Notifications, which Alex will dive more into in the following section, you can keep up-to-date with the latest status and state of a subscription without having to call the App Store Server API. When you do need to perform an operation on a subscription, such as extending the renewal date of a subscription, you can use the originalTransactionId from the signed transaction to call corresponding endpoints and get back the data that you need. Now that we've seen how to use the App Store Server API with original StoreKit and StoreKit 2, let's talk about how to support both StoreKit and StoreKit 2. You can take advantage of the App Store Server API without fully adopting StoreKit 2. As shown previously, you can get the originalTransactionId from a receipt in Original StoreKit. You can also get the originalTransactionID in StoreKit 2 from a JWS transaction.
You can also use the App Store Server API independently of any other APIs. It is not tied to using a specific version of other APIs. In terms of App Store Server Notifications, it can be used with either version 1 or version 2 notifications. We do recommend using version 2 because it notifies you of changes to subscriptions as they occur, uses the secure JWS format, and more, which Alex will dive into more in his part of this session. However, you can use the App Store Server API separately, with version 1 notifications or without notifications at all. Next, let's discuss how you can process new purchases after you completed the migration steps I previously walked through. To support new purchases on devices using original StoreKit, you can take new receipts as they come, sending them to your server, and do precisely the same steps as I previously showed, while collecting new data along the way– call verifyReceipt with the new receipts, and get the decoded receipt with the new originalTransactionId in latest_receipt, correlate those originalTransactionIds with other originalTransactionIds in the in_app section of the receipt to group together your transaction information. Then, you can take the new originalTransactionId, and call the App Store Server API as needed, such as if you need to call the Get All Subscription Statuses endpoint to get you the latest status of the corresponding subscription. Now that we've covered how to use the App Store Server API with both Original StoreKit and StoreKit 2, let's dive into some of the details of signing JSON Web Tokens, a requirement to call the App Store Server API. In order to authenticate that your developer account is the caller of the App Store Server API, we use JSON Web Tokens, also known as JWTs, to authenticate requests. This token must be included in every request as an authorization header in calls from your server. A JWT consists of a header, a payload, and signature. Next, we'll go into how to construct a JWT specific to your application.
Here, we can see how a JSON Web Token is composed, as well as the structure of the header and payload. The token itself can be broken into three parts, separated by periods: the base 64 encoded header, the base 64 encoded payload, and then the signature, which is composed of the base 64 encoded header and payload, signed using your signing secret. The header is composed of these fields that contain metadata about how to sign your data. One of the important fields here is the key ID, which is your private key ID in App Store Connect. This needs to match the key you use to sign the JWT.
The payload contains additional information about your specific application. Please refer to the article "Creating API Keys to Use With the App Store Server API" for additional information and guidance on how to obtain your API Key. For details on each of these fields, please refer to the article "Generating Tokens for API Requests." Once you have the header and payload with all the appropriate information, next you will sign the JWT using the certificate that corresponds to the keyId. Here is the core pseudocode that you can use, regardless of language. First, make sure that you have the private key that corresponds to the key id provided in the header that we just looked at. Then, call the signing function that your JWT library exposes with your private key, header, and payload. Since the header contains the signing algorithm, the JWT library signs it according to the provided algorithm.
The first certificate is referred to as the root certificate and is self-signed. This certificate should match the root certificate you obtain from Apple's Certificate Authority. If the certificates do not match, the chain should not be trusted. The leaf certificate, the last certificate in the chain, is the certificate that is used to sign the JWS. Here is an example of what the header of a JWS the App Store sends may look like. First is the algorithm used to sign the JWS. Next is the x5c certificate chain, with the certificates listed in order.
Now, let's look at what generating an x5c certificate chain looks like from a high-level overview. We start off with the root certificate from Apple's Certificate Authority. Then, the root certificate is used to sign the intermediate signing certificate. The intermediate signing certificate is then used to sign the leaf certificate.
Now that we've covered what generating an x5c certificate chain looks like, let's look at what verifying a chain looks like. Starting at the leaf certificate, we ensure that it was signed by the intermediate signing certificate. Then we ensure that the intermediate signing certificate was signed by the root certificate. Additionally, the root certificate should match the one from the Apple Certificate Authority. If all of these steps are successful, then the entire chain is verified as legitimate. Let's talk about a method to verify the certificate chain. Here is a command to verify the x5c certificate chain using OpenSSL. Breaking this into pieces, the command verify, broadly speaking, allows you to pass in certificates for verification. The flag trusted allows you to provide a certificate to trust– in other words, a certificate that will be used to verify the following certificates. in this case, we are passing in the root certificate you obtained from the Apple Certificate Authority, and thus can be trusted. We'll use this to verify the WWDR certificate, the next certificate in the chain.
The untrusted flag allows you to provide the certificate or certificates that you wish to verify using the certificate that you trust. Here, we first pass in the WWDR certificate from the Apple Certificate Authority, which is signed by the root certificate. This should match the second certificate in the x5c chain. And finally, the leaf certificate here is the last certificate, which is signed by the previous certificate. In the case of a successful verification, a success code is returned. You may then proceed to use the decoded information. In the case of an unsuccessful verification, determine the issue based on the returned error code. If not possible to verify, this data may be tampered with and should not be used. Please refer to the App Store developer documentation for complete instructions on verifying an x5c certificate chain using OpenSSL.
Here is some pseudocode for how one might go about verifying a signed transaction. First, obtain the JWS you wish to verify. Then, take the certificate your JWS library requires for verification. Call the verify function of your JWS library, using the appropriate certificate. The certificate that signs the JWS is the leaf certificate, though some libraries require passing in the entire chain.
In the case that the call succeeded, then you can proceed on with your tasks. In the case that this was the result of a call to the App Store Server API, then you can proceed to store the validated data. As for the case of notifications, Alex will go more into this in his portion of this session. In the case that the JWS cannot be validated, do not use the JWS. This may mean that it has been tampered with or was not sent by the App Store. Alex will dive more into how to better ensure security when using notifications. Please refer to the App Store developer documentation for complete instructions on verifying and handling a JWS.
Now, let's review some use cases for migration from verifyReceipt to the App Store Server API. First, let's look at the case where you want to check what the latest status is for any given subscriber. This keeps you up to date with any changes to an individual subscription. Previously, to obtain the most up-to-date status of a subscriber, one had to call verifyReceipt and determine the status of the subscription based on fields such as expiration intent, grace_period_expires_date, et cetera. Now, with the App Store Server API, the Get All Subscription Statuses endpoint can be called to obtain the latest status of a subscription, with a status field containing the current status, as well as the latest, most up-to-date signed transaction and renewal information. Let's look at a flow of how you could execute on this. First, for any decoded receipt that you have, you can obtain the originalTransactionIds from it just in the way that I showed previously. Then, you can call the Get All Subscription Statuses endpoint for that originalTransactionId, which will return the latest signed transactions and renewals for that transaction. Next, let's look at the case of obtaining the latest transactions. Obtaining the latest transactions informs you of what a user has purchased, what has renewed, if there are any changes to a user's subscription, and more. Previously, to obtain the latest transactions for a user, one had to call verifyReceipt and use the in_app array and examine latest_receipt_info, which contained all of the transactions for a user. With the App Store Server API, to obtain the latest transactions, the Get Transaction History endpoint allows you to fetch the full purchase history for a user. Furthermore, pagination combined with the new filter and sort features that is covered in the WWDC22 talk, "What's new with in-app purchase," ensures that you can efficiently fetch precisely the data that you need.
Let's look at a flow of what this may entail. With any originalTransactionId belonging to that user, you can call the Get Transaction History endpoint, which will return the history of transactions for this user as signed transactions, filtered, sorted, and paginated to your specifications.
Finally, let’s look at the case of adopting appAccountToken. The appAccountToken field allows you to provide a UUID that associates a StoreKit 2 transaction with a user. Then, on signed transactions, signed renewals, and notifications for that transaction, the appAccountToken will appear. Previously, there was not support for appAccountToken with original StoreKit, , as it was a feature that was new to StoreKit2. Now, we added support for supplying a UUID in the field applicationUsername in Original StoreKit to support compatibility with Original StoreKit clients. Under this condition, that UUID will support all the functionality that appAccountToken does. The appAccountToken then comes back in verifyReceipt for Original StoreKit users, and also appears for both Original StoreKit and StoreKit 2 users in calls to the App Store Server API and notifications from App Store Server Notifications.
That's it for the App Store Server API portion of this session. Next, here's Alex to cover migrating to App Store Server Notifications Version 2. Alex: Thanks, Gabriel. My name is Alex, and I'm excited to be here today to discuss App Store Server Notifications Version 2. First, we'll be covering how to get started with version 2 notifications. Next, how version 2 notifications differ and build upon other models available. Third, we'll talk about recovering in the case of missed notifications and some of the new resources available to help accomplish this task. Last, how notifications can provide insight into customer behavior and create additional opportunities for being informed about the subscription lifecycle. Let's go over a brief introduction into what notifications are and who can use them. App Store Server Notifications are messages we send you whenever certain actions are taken by users of your app. These notifications broadly fall into two categories, subscription updates and refund updates, although we're always working to cover additional scenarios. We provide these notifications to help fill in gaps into user actions that may not be available to you in the app. As an example, one of our most common use cases is the renewal of a subscription. A user may not be in the app when this transaction becomes available. App Store Server Notifications help account for this issue by proactively sending the latest transaction information directly to your servers when the subscription renews. Version 2 notifications share many similarities with the StoreKit 2 model and the App Store Server API you just heard about from Gabriel. However, while they work well together, they are all independent tools that can be adopted at different times. Most importantly, you can continue to support clients where StoreKit 2 is not available, pre iOS-15 clients, while utilizing Version 2 Server Notifications. We've worked to make Version 2 notifications one of our most in-depth and flexible tools for providing information about a user throughout the entire subscription lifecycle. We'll go more in-depth in this later in the presentation, but notifications provide information that is almost impossible to capture for actions taken outside of the app. I hope I've interested you in the concept of notifications and Version 2 notifications in particular. Before we go further, while this presentation walks you through getting started and best practices for receiving notifications, it doesn't tell the whole story. Please refer to these recent videos for more information about notifications and how they can meet various use cases. Let's look at getting Version 2 notifications set up. We're going to walk through how to set up your notifications all the way to receiving your first one. First, go to your app's page in App Store Connect. Scrolling down, you will see a section for App Store Server Notifications. Here you'll see options for both production and sandbox. Each environment can contain a separate URL and a separate notification version. Here is an example of the options page for production settings. Sandbox settings are exactly the same. We recommend, especially if you are a Version 1 notification user, that you first try Version 2 notifications in the sandbox environment. This is a great place to become comfortable with notifications without impacting your production setup. Select the Set Up Sandbox button, provide your server's URL, and select Version 2 Notifications.
Before triggering notifications, confirm you have a valid HTTPS certificate for your server endpoint. Also confirm you have allowed Apple's public IPs access to your server. Some of the most common failures when setting up notifications relate to firewalls and certificates. These are also great to check if you suddenly stop receiving notifications as an initial troubleshooting step. Now you are ready to receive your first notification. In Sandbox, notifications can be triggered by a variety of actions, like buying an in-app subscription. However, for ease of use while testing, we recommend triggering a notification using the new Request a Test Notification endpoint, part of the App Store Server API. This endpoint helps to automate the notification testing process. After triggering the Request a Test Notification endpoint, you should expect to see a notification arrive soon. If you are having issues receiving notifications, please refer the to new Get Test Notification Status endpoint, which can provide a brief status about why the notification failed to be delivered. For example, a status like SSL_ISSUE would be a clue to double-check your HTTPS certificates. We recommend triggering a test notification whenever you are performing a configuration change. This is a great way to confirm you can still receive notifications after the change. Now, let's move on to understanding the notification you've just received.
Just like transactions we saw earlier from Gabriel, notifications are also in the JWS format. Let's go over how to decode and verify a notification payload. First, when receiving a notification, you'll want to extract the signedPayload field of the JSON body. Next, you'll perform the exact same steps Gabriel walked you through earlier for verifying a signed transaction. You'll follow the same steps to verify signed data whether it is a signed notification payload from a notification or a signed transaction from the App Store Server API. Next, it is important to verify which app the notification is for. If you have multiple apps sharing the same endpoint, this is a good place to determine the target app. It is also important to confirm that the app the notification is targeted for is your app, and the notification was not intended for another developer. Last, one more useful check is to make sure that the environment of the notification matches your expected environment, either Production or Sandbox. Because App Store Connect allows separate URLs for each environment, it is possible to enforce this requirement, or, if the URLs are shared, guarantee you are storing and processing notifications separately based on the environment. At this point, the JWS is fully validated and can be stored for further processing. We recommend, besides some basic checks, that your server process the notification asynchronously. If processing of the notification takes too long, our server will record a timeout and assume the notification was not delivered successfully. We will then resend the notification. Therefore, moving time-intensive processing outside this function helps to ensure that the App Store server records your notifications as sent successfully and removes the need for your server to reprocess the notifications upon a retry. Now, let's go over the body of the notification after verification. The first fields are notification type and the optional subtype. Combined, these tell you the scenario the notification is for. These fields also help show what has changed since the last notification and provide information about why these changes occurred. The notificationUUID is a unique identifier per notification. If the server retries a notification, the retried notification contains the same notificationUUID. This helps detect cases where your server processed the notification, but did not respond with a successful HTTP response code in a timely manner. We recommend adding duplicate notification detection due to retries, based on this field. The signedDate field tells you when the notification was created. This is especially useful for detecting retried notifications. Next, the appAppleId and bundleId. These are important for detecting the target application. As we discussed earlier, it is important that you check these fields and confirm they match expected values to prevent replay attacks. Additionally, make sure the environment of the notification matches the expected environment, that sandbox notifications aren't being recorded as production data, and vice versa.
Last, the actual signedTransactionInfo and optional signedRenewalInfo. These will be the latest status of the underlying purchase at the time of signing. At this point, having parsed the notification, you are left with the latest transaction and renewal information, and the latest reason for the change in status. Now that we've covered setting up and receiving a specific notification, let's examine the Version 2 notification model, how notifications can fit together to track the subscription lifecycle, and the design decisions behind Version 2 notifications through a comparison with Version 1 notifications. Version 2 adopts a different philosophy when sending information about the state of a purchase. Instead of sending the entire recent history every notification, Version 2 notifications focus only on sending the latest information: the latest transaction information, and in the case of subscriptions, the pending renewal information as well.
With notifications, we work to provide information on every step of the subscription lifecycle. Therefore, notifications only contain the latest information about the purchase or subscription. Together, these notifications create a complete timeline of a subscription's status. If you need to view the entire transaction history and don't have access to the notification history, this pairs well with the Get Transaction History endpoint which lets you query a user's entire transaction history in a paginated and filterable context. Second, version 1 notifications do not require clients use StoreKit 2. And that's right, neither does version 2. In fact, no matter what framework is used on the client side, you can start enjoying the benefits of version 2 notifications today. Last, version 2 notifications work to enhance the level of detail provided and expand cases covered by adding additional types and adding the new subtype field. Through this, we are able to cover more scenarios and provide notifications at every step of the subscription lifecycle. Some of the notable scenarios we've added include expiration, more granular information related to changes in auto renewal status, and more scenarios around the refund process. Now, to illustrate the complexity of the scenarios covered and provide a more concrete example, let's look at how notifications can inform you of each step a subscription takes, from start to finish. Let's imagine a user before a subscription. Upon subscribing, the user moves into the renewing subscription state and a SUBSCRIBED with subtype INITIAL_BUY notification is sent, or an OFFER_REDEEMED with subtype INITIAL_BUY if an offer was used. Contained in the notification would be the first signed transaction and the signed renewal information. Time passes, and the subscription renews, staying in the renewing state. Upon each renewal, we send a DID_RENEW notification with the next signed transaction information. Whenever the user deactivates auto renewal, they move to the expiring subscription state, and you will receive a DID_CHANGE_RENEWAL_STATUS with subtype AUTO_RENEW_DISABLED notification.
If they don't reenable auto renewal, at the end of the period they move to the expired state, and you will receive an EXPIRED with subtype VOLUNTARY notification. Now, you might be wondering, where are all the other notification types? Here is the subscription lifecycle, as seen through notifications. There is a lot going on. And this diagram doesn't even tell the whole story. The refund/revocation lifecycle isn't included here, for example. This diagram illustrates the vast array of scenarios that version 2 notifications cover and that they work to inform you of each step of the subscription lifecycle.
The other point I would make is that we work to cover every possible transition state. This helps to increase the utility of notifications by becoming a single source for tracking subscriptions and improves confidence that you are seeing every step of the subscriber's journey. However, even though all of this data is here, you don't need to work with every type available. Even just processing notifications related to renewal preference changes, for example, can provide value. Especially if you are just getting started, begin with the notification types that are most useful for your situation. Now, let's cover what happens after you've got your server set up, everything is running smoothly, but, alas, your server goes down. Whether it was for a few days, a few minutes, or you think you might have missed just one, let's go over some steps to help resolve this issue. Let's image your server. It is set up successfully and is receiving notifications. At some point, your server has an issue and is unable to receive notifications. We are still attempting to send messages to your server, but now those requests begin to fail. There are several ways to deal with this scenario. The first is just to wait. If we don't receive a successful status code from your server or couldn't connect to it at all, we will retry notifications according to our documented retry policy. For version 2 notifications, we retry after each attempt, first after a 1-hour delay, then a 12-hour delay, 24-, 48-, and finally 72-hour delay. Waiting works great for outages less than an hour, as notifications will be retried an hour after the first failure.
At some point your server recovers, and you start receiving notifications again. First, you receive a new notification, unrelated to the missed notifications. Notifications are retried with a delay, so as soon as your server comes online, you will not immediately receive all missed notifications.
Some time passes and you start to receive the notifications you missed, interspersed with new notifications.
This brings up the question, how can you detect if a notification is the original or a retried notification? Let's examine a notification.
In this notification, we're just showing a few fields.
Notifications contain a signedDate field. This field can be useful for detecting retries, by comparing the signing date with the time the notification was received. If you see notifications with a signing date significantly earlier than the date you received the notification, this indicates you may have experienced an outage.
Imagine in this scenario the notifications labeled 6 and 3 were for the same subscription. This could be determined by comparing the originalTransactionIds. In this case, just because notification 3 was received after notification 6 does not mean it contains newer information than notification 6. Other times, a notification may have been received by your server, but it failed to respond with a successful HTTP 200 status code. This can cause a notification to be redelivered to your server. As discussed earlier, make sure to check the notificationUUID field to deduplicate these requests. You may see significant numbers of retried notifications even though you successfully recorded the notifications. In this case, make sure you are responding with an HTTP 200 response every time you receive a notification. Additionally, make sure that you are doing so in a timely manner, and are not doing extensive processing before responding successfully, to prevent us from recording a timeout and resending the notification. Sometimes, especially with longer outages, the next retry may be hours or days away, or for an extended outage, retries may have been exhausted. The next option for recovering from missed notifications is the Get Notification History endpoint.
We've just announced the new Get Notification History endpoint, which provides a six-month history of notifications we have sent your server. Refer to "What's new with in-app purchases" video for an overview of this endpoint along with other great features we are announcing. Here we will be focusing on best practices when using this endpoint and scenarios where it can assist. After an outage is resolved, note the start and end timestamps of the outage. The Get Notification History endpoint allows queries to be made over a specific timespan. By specifying the start and end times of the outage, you can only process notifications that you likely missed, instead of requiring paging through the entire history. This will help improve the speed of recovery and reduce work reprocessing already-recorded notifications.
Next, the Get Notification History endpoint allows you to filter by type of notification. If you have experience an extended outage and expect a significant number of notifications, consider filtering by type, and starting with types that may have immediate impact, like DID_RENEW and EXPIRED. These will help you take action on the most relevant cases first. One tip when passing notification types, if the notificationSubtype field is omitted, this will only return notifications that also do not have a subtype. Therefore, for the example shown for the DID_RENEW notificationType, this would not return DID_RENEW notifications with subtype BILLING_RECOVERY.
Last, the Get Notification History endpoint allows filtering to a specific user by using an originalTransactionId. Thinking back to the subscription lifecycle, we've worked to make sure every step of a user's journey is covered by notifications. Therefore, if you find yourself jumping around in unexpected ways, for example from a renewing subscription straight to expiration, this may indicate that you missed a notification for that user. This can also be useful in a customer-support context if a user's account is in a different state than you would expect. In these cases, you can send a query for that user's notification history.
Let's go over the response from the Get Notification History endpoint. Only certain values are shown in the response for simplicity.
The values returned in the response are in the notificationHistory array.
Each entry in the array represents a single notification.
The signed payload field contains the exact notification that was sent to you.
Second, we have the firstSendAttemptResult field. This field contains one of several values based on the result of the initial notification attempt as recorded by our servers. In the successful case, this will be the value SUCCESS. However, as we've just been discussing, sometimes notifications fail to reach your server. These messages are meant to be a general guide to help point you in the direction of the issue, to simplify the resolution process. For example, we see SSL_ISSUE here. This indicates there is a problem with the SSL certificate or process on the server. This field provides improved visibility into diagnosing servers issues beyond seeing the notification did not arrive. We also provide this same field in the Get Test Notification Status endpoint, to provide this functionality when using test notifications. These can be used to help while onboarding or during troubleshooting, or retrospectively during determining the root cause of an outage. Notifications may not cover all cases of a user's history. You may have just adopted notifications and have existing users with histories not covered. You also may wish to examine a history longer than the retention period of notifications in the Get Notification History endpoint. That's where the Get Transaction History endpoint enters the picture. This endpoint, as we saw earlier in the presentation from Gabriel, solves these issues by providing histories for your customers that cover cases before you started using notifications. Now, let's go over how notifications can provide insight and opportunities above and beyond the purchase history. One of the new additions in Version 2 notifications is the subtype field, adding additional context to the notificationType field. This field is meant to provide more detail in certain scenarios, like EXPIRED or DID_CHANGE_RENEWAL_STATUS. For example, with EXPIRED, the action you take is usually the same, mark the subscription as inactive and revoke access to the product. However, it can often be useful to understand why the user expired. Was it due to a billing issue, voluntary choice, or a price increase that was never accepted? Another notification, DID_CHANGE_RENEWAL_STATUS, is a great example of gaining additional information and opportunities when using notifications. On the surface, it looks to be of low priority. No action needs to be immediately taken. The important notification for revoking access to the product is the EXPIRED notification. Don't be fooled. There is a lot of opportunity here. One, this notification is a great opportunity to attempt to win back the customer before their subscription expires. Especially since deactivating auto renewal may occur outside the application, this can be the only trigger to be informed in this change in renewal status before the expiration date. This notification also provides insight into customer behavior. This notification can be used to determine when in the renewal period subscribers are cancelling. Is it the day before renewal? Are new subscribers deactivating auto-renewal soon after signing up for your service? This type of information can be important for understanding the causes of cancellation and improving your product. Last, certain scenarios may never be reflected in a user's history without notifications. For example, a user may deactivate but then reactivate auto-renewal, before their subscription period has expired. Because this all occurs within a subscription period, there is no effect on the subscription's long-term status. These decisions can be important for understanding your customers, and notifications provide information to detect and record these types of scenarios. Overall, notifications work to enhance and create opportunities for understanding customer behavior by providing information at every step of the customer journey, covering more scenarios than ever before. In conclusion, today we've covered both the App Store Server API and App Store Server Notifications. These are available to improve the capabilities surrounding managing and tracking purchases. They use updated message types and cover more cases than ever before. These systems are available for all clients and cross-compatible with both Original StoreKit and StoreKit 2 and can improve your ability to monitor the subscription lifecycle. Last, these tools are already available in both Sandbox and Production and are a great addition to any system. Thank you for joining us, and have a great 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.