How to get and decode AppStore Server Notifications v2 using JWT, PHP
To receive and decode App Store Server Notifications using JWT in PHP, you need to set up a webhook endpoint in your PHP application to receive incoming notifications. Follow these steps to get and decode App Store Server Notifications:
1. Set up an Endpoint/URL:
– Create a PHP script that will serve as your webhook endpoint/URL to receive incoming notifications from the App Store.
2. Handle Incoming Notifications:
– In your endpoint/URL PHP script, use `file_get_contents(‘php://input’)` to get the raw request data, which contains the signed JWT notification.
– Decode the JWT token using your App Store Shared Secret and verify the signature.
– Handle the decoded notification data according to your application’s requirements.
Here’s a basic example of how you can handle incoming notifications in your webhook PHP script.
$notificationPayload = file_get_contents('php://input');
The above code will get the response from AppStore Server in JSON Web Signature (JWS) format will look like this:
Verify the JWS By Public Key(.PEM) and with JWT Library
- You need the public key associated with the private key used by Apple to sign the payload. This public key is typically provided by Apple and can be found in your developer account.
- Verify the JWS signature using the public key and the JWS header and payload data.
if you can’t find the public key then download the Apple root certificate and make it.PEM file from it.
Download the certificate below URL:
https://www.apple.com/certificateauthority/AppleRootCA-G3.cer
Then using the OpenSSL command convert this certificate into.PEM file:
openssl x509 -in AppleRootCA-G3.cer -out apple_root.pem
The next step is to run the script below and decode the JWS payload.
function decodeJWSDataWithPemKeyAndJwt($jwsData) { //------------------------------------------ // Loading PEM KEY //------------------------------------------ $pem = file_get_contents("YOUR_DIRECTORY/apple_root.pem"); $header_payload_secret = explode('.', $jwsData->signedPayload); //------------------------------------------ // Header //------------------------------------------ $header = json_decode(base64_decode($header_payload_secret[0])); $algorithm = $header->alg; $x5c = $header->x5c; // array $certificate = $x5c[0]; $intermediate_certificate = $x5c[1]; $root_certificate = $x5c[2]; $certificate = "-----BEGIN CERTIFICATE-----\n" . $certificate . "\n-----END CERTIFICATE-----"; $intermediate_certificate = "-----BEGIN CERTIFICATE-----\n" . $intermediate_certificate . "\n-----END CERTIFICATE-----"; $root_certificate = "-----BEGIN CERTIFICATE-----\n" . $root_certificate . "\n-----END CERTIFICATE-----"; //if (openssl_x509_verify($intermediate_certificate, $root_certificate) != 1){ //echo 'Intermediate and Root certificate do not match'; //exit; //} // Verify again with Apple root certificate if (openssl_x509_verify($root_certificate, $pem) == 1) { $cert_object = openssl_x509_read($certificate); $pkey_object = openssl_pkey_get_public($cert_object); $pkey_array = openssl_pkey_get_details($pkey_object); $publicKey = $pkey_array['key']; //------------------------------------------ // Payload //------------------------------------------ $payload = json_decode(base64_decode($header_payload_secret[1])); $transactionInfo = $payload->data->signedTransactionInfo; //$notificationType = $payload->notificationType; //$signedRenewalInfo = $payload->data->signedRenewalInfo; // Decode the JWT using the public key try { $transactionDecodedData = JWT::decode($transactionInfo, new Key($publicKey, $algorithm)); //var_dump($transactionDecodedData->originalTransactionId); //$signedRenewalDecodedData = JWT::decode($signedRenewalInfo, new Key($publicKey, $algorithm)); //var_dump($signedRenewalDecodedData); return $transactionDecodedData; } catch (Exception $e) { echo 'Header is not valid'; exit; } } else { echo 'Header is not valid'; exit; } }
Verify the JWS Without JWT Library
function decodeJwsWithNoOpenSsl($jwsData) { $jws = json_decode($jwsData); $header_payload_secret = explode('.', $jws->signedPayload); $payload = json_decode(base64_decode($header_payload_secret[1])); if(!empty($payload)) { $notificationType = $payload->notificationType; $notificationSubType = $payload->subtype; //Decoding $jwtTransaction $jwtTransactionHeader = base64_decode(explode('.', $payload->data->signedTransactionInfo)[0]); $jwtTransactionPayload = base64_decode(explode('.', $payload->data->signedTransactionInfo)[1]); //Decoding Renewal Data $jwtRenewalHeader = base64_decode(explode('.', $payload->data->signedRenewalInfo)[0]); $jwtRenewalPayload = base64_decode(explode('.', $payload->data->signedRenewalInfo)[1]); //Return the response return [ 'transaction' => $jwtTransactionPayload, 'renewal' => $jwtRenewalPayload, 'noteType' => $notificationType, 'subType' => $notificationSubType ]; } else { echo 'Header is not valid'; exit; } }
Note:
AppStore JWS signature Data, which includes a signed payload, signedTransactionInfo, notificationType, notificationSubType, and signedRenewalInfo. The algorithm which we can get from the header’s alg parameter to verify the JWS signature. For more see the JSON Web Signature (JWS) IETF RFC 7515 specification.
The response may look like this: