Site icon IT Shytee

App Store Server Notifications V2 with JWT, PHP

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

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:

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';

    // 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));

            //$signedRenewalDecodedData = JWT::decode($signedRenewalInfo, new Key($publicKey, $algorithm));

            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;


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:

Exit mobile version