App Store has released server-to-server notification version 2 with more events(notification types and sub types) than version 1.
In version2, they are sending JSON Web Signature (JWS) formatted (Which is a JWT token) notification to improve the security of data.
In this article, we will show how to decode SNS notifications v2 in a go application.
When I started implementing app store SNS v2 in golang, I was a bit confused about how to verify and parse JWS data.
Thanks to this thread, I learned lots of important things there. While doing the implementation, I simplified it to make understanding easier.
This article will help you learn about SNS implementation in a very simple way.
Before beginning with the actual implementation, let’s get familiar with the request body of the notification we get from apple first!
Notification V2 request body
It’s signed by the App Store and contains all required data including
transaction info and
renewal info of app store purchases.
FYI : You can parse and review
Whenever we receive a notification from the app store, we need to first verify the request and then we need to parse that token to get payload data.
JWS contains 3 parts in the token separated by
- Header (contains two fields :
- Payload (contains data)
For verifying the request we need to parse the header part. If it's verified successfully, then we will parse the payload otherwise decline it.
We have divided the decoding process of
signedPayload in the following steps —
- Extract header from JWS token
- Verify header with an app store key
- Extract the public key from the token to parse payload data
- Prepare structures to bind notification
- Parse payload and bind it to structures
Let’s learn it step by step.
1. Extract header from JWS token
First, we will define the structure to get a header with two fields algorithm and token type.
- Algorithm: ES256
- Token type: x5c (X.509 certificate chain) contains the X.509 public key certificate or certificate chain [RFC5280] corresponding to the key used to digitally sign the JWS.
Let’s understand it,
- strings.Split: split string by
.and extract the first part of the split array which is a header.
- base64.RawStdEncoding.DecodeString(tokenArr) decodes base64 header string to a byte with raw encoding.
- Unmarshal bytes to go structure which is
NotificationHeader. We need to use X5c of structure to get the certificate.
X5cis an array of 3 elements which is a certificate chain.
X5c: use for extracting public key
X5c: intermediate certificate used to verify the header
X5c: root certificate used to verify the header
2. Verify header with an app store key
In this step, we will verify the
X5c certificates using the app store key, which we can download from Apple Root CA — G3 Root.
Convert it to PEM using the following command.
openssl x509 -in AppleRootCA-G3.cer -out cert.pem
Verify the X5c certificate using the app store key.
- x509.NewCertPool() will create a new and empty certificate pool, and we will append the app store cert to this pool.
- APP_STORE_NOTIFICATION_ROOT_CERT is converted
PEM keyfrom the app store certificate. Copy the PEM key from the file and assign it here.
- roots.AppendCertsFromPEM parse and append the PEM key to the cert pool.
- We also parse and append intermediate certificates to the certificate pool.
- x509.ParseCertificate(certByte) parses extracted
- x509.VerifyOptions prepare x509 verify options.
- cert.Verify(opts) verifies X5c certificate using verifyOptions.
3. Extract the public key from the token to parse payload data
Now we have verified the notification request, it's time to parse payload data. For that, we need a public key. Let’s get that from the header.
We have reused code from
step 1 and
step 2 and extract the public key from the header.
- Get the certificate from
- Parse certificate string to bytes.
ecdsa.PublicKeyfrom certificate bytes.
That’s it. You will have the public key now.
4. Prepare structures to bind notification
When we parsed the JWS token on jwt.io, we see that payload has some fields. We will define go structures for required fields from those fields.
From the payload, I have identified 3 basic structures that we will require to change the user’s subscription status.
You can understand all the fields on the app store’s official documentation.
You have noticed
jwt.StandardClaims in all the structures as we need them to parse JWS data.
Let’s go to the final step.
5. Parse payload and bind it with structures
Here, We have used the public key method from step 3 to parse the JWS token string.
- In notificationPayload, we have parsed the main
signedPayloadstring. It has a
datafield, which contains
renewalInfoin JWS format string.
- In TransactionInfo, we have parsed
- In renewalInfo, we have parsed
The above steps are used to decode the notification payload signed by the app store.
But we have to do that in order as we have discussed earlier, if the certificate is verified, then only we will parse data otherwise declined it.
Now we have
renewalInfo, Using these data we can update the user’s subscription status based on the
notificationType and it’s
That’s it for today. Hope you have an understanding of how we can decode SNS v2 notification data and use it at the server. Similar way, you can implement SNS v2 in any backend language like ruby, PHP, or python.
You can refer to app store documentation to decide what user’s data you have to update at your backend from notification.
Feedback and suggestions are always welcome. If you have any, feel free to add them in the comments section.
How to do App store IAP verification on the server-side with S2S(server-to-server) notifications
There are four types of in-app purchases provided by apple, as described here. This article describes how to…