How to send HTTP/2 based Push Notifications in iOS

Apple supports push notifications based on the HTTP/2 network protocol. In fact, the legacy binary protocol on which the previous LiveCode lesson was based, will no longer be supported, as of April 2021.

In this lesson, we will show how to send http/2 push notifications to an iOS app, using token authentication.

This lesson is structured into the following 5 logical sections:

1. Creating an App ID in your developer account and enabling the push notification entitlement.

2. Generating the signing key

3. Creating the Push Notification Provisioning Profile

4. Writing a LiveCode Application with Push Notification Support

5. Sending a Push Notification

1. Creating an App ID in your developer account and enabling the push notification entitlement.

Visit https://developer.apple.com/account and then go to Certificates, Identifiers & Profiles. Click on Identifiers and press the + icon to create a new identifier:

Choose App IDs and press Continue.

Choose App and press Continue.

Provide a Description and a Bundle ID. Ensure the Bundle ID does not contain an asterisk (*). Scroll down, and in the Capabilties section, enable Push Notifications, and click Continue.

Now click Register.

The new app ID is now register. Now go back to the list of all app IDs, you should see the ID you just created:

2. Generating the signing key

Now go to the Keys section at the left pane:

Choose Create a key:

Provide a Key Name, enable Apple Push Notifications service (APNs), and click Continue

Then click Register

and then click Download

Store your key in a safe place. Make sure you write down your Key ID as well.

The key is a .p8 file, and its name should be something like AuthKey_<Key_ID>.p8

3. Creating the Push Notification Provisioning Profile

Each application that supports Push Notifications requires its own Provisioning Profile. The following steps lead you through the process of creating the Provisioning Profile for your application.

Log into your Apple Dev account and select Profiles on the left hand navigation panel. Click on the + button:

Choose iOS App Development and click Continue

In the App ID dropdown, choose the App ID you created in section 1 of this lesson, and click Continue.

In the next screen, choose the certificate(s) that you want to include in this provisioning profile, and click Continue.

In the next screen, choose the test devices on which the Provisioning Profile should operate, and click Continue.

Then provide a Provisioning Profile Name and click Generate.

In the next screen, click on Download

Then, double click on the downloaded profile to open it with Xcode. This will ensure that the profile will be selectable from within LiveCode.

4. Writing a LiveCode Application with Push Notification Support

Now that you have completed all of the work that is needed to support Push Notifications in LiveCode, it is time to implement code that acts upon a notification. Push Notifications are delivered by LiveCode in form of a message. Add the following or similar code to the stack script of your application:

on pushNotificationReceived pMessage
	  answer "Push Notification Message:" && quote & pMessage & quote with "Okay"
end pushNotificationReceived

Note: Look this message up in the dictionary and follow links to related dictionary entries.

Starting the application

The first time you start your Push Notification enabled LiveCode application you should get a message that is similar to the one shown here. The message only ever appears the first time the application is launched and does not appear again. This indicates that the iOS device is aware that the application can received Push Notifications and sets up the necessary parameters to allow notifications to be delivered to the application.

Getting the device identifier

The Device Identifier can be retrieved using the following LiveCode function:

mobileGetDeviceToken
answer the result with "Okay"

The Device Identifier is submitted to the server when registering for Push Notifications and provides the means by which the iOS device is identified when the notification is sent from the server.

Note: You don't get a deviceToken until after you get a Registered message. You can check this with the pushNotificationRegistered function

 

on pushNotificationRegistered pToken
   put ptoken into fld 1
end pushNotificationRegistered

5. Sending a Push Notification

Push Notifications are normally sent from a server, but we can simulate this action from a terminal. You can use the following code example as a template. Update the the first 6 lines with your own details and save it to a .sh file. In this example we are calling the file: send.sh

TEAM_ID=CDI79NSGHP
TOKEN_KEY_FILE_NAME=/Users/panos/Documents/PushNotificationAppAssets/NEW/AuthKey_9S733W8972.p8
AUTH_KEY_ID=9S733W8972
TOPIC=com.livecode.pushNotificationAppHttp2
DEVICE_TOKEN=62dfb121dfaa34c014rra9b4ed2d8ebcda43ed0ca7fafa8b7f45c512d60cdd30
APNS_HOST_NAME=api.sandbox.push.apple.com

JWT_ISSUE_TIME=$(date +%s)
JWT_HEADER=$(printf '{ "alg": "ES256", "kid": "%s" }' "${AUTH_KEY_ID}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
JWT_CLAIMS=$(printf '{ "iss": "%s", "iat": %d }' "${TEAM_ID}" "${JWT_ISSUE_TIME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
JWT_HEADER_CLAIMS="${JWT_HEADER}.${JWT_CLAIMS}"
JWT_SIGNED_HEADER_CLAIMS=$(printf "${JWT_HEADER_CLAIMS}" | openssl dgst -binary -sha256 -sign "${TOKEN_KEY_FILE_NAME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
AUTHENTICATION_TOKEN="${JWT_HEADER}.${JWT_CLAIMS}.${JWT_SIGNED_HEADER_CLAIMS}"

curl -v --header "apns-topic: $TOPIC" --header "apns-push-type: alert" --header "authorization: bearer $AUTHENTICATION_TOKEN" --data '{"aps":{"alert":"You have a new message"},"payload":"Hello from LiveCode"}' --http2 https://${APNS_HOST_NAME}/3/device/${DEVICE_TOKEN}

From a terminal run your .sh file using this syntax (replace the file name with the name you created for the code):

sh /path/to/send.sh

panoss-MBP:~ panos$ sh /Users/panos/Documents/PushNotificationAppAssets/NEW/send.sh
*   Trying 17.188.166.29...
* TCP_NODELAY set
* Connected to api.sandbox.push.apple.com (17.188.166.29) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=api.development.push.apple.com; OU=management:idms.group.533599; O=Apple Inc.; ST=California; C=US
*  start date: Apr 17 17:37:51 2019 GMT
*  expire date: May 16 17:37:51 2021 GMT
*  subjectAltName: host "api.sandbox.push.apple.com" matched cert's "api.sandbox.push.apple.com"
*  issuer: CN=Apple IST CA 2 - G1; OU=Certification Authority; O=Apple Inc.; C=US
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fdfa9002c00)
> POST /3/device/62dfb121dfaa34c01yhui9b4ed2d8ebcda4e140ca7fafa8b7f45c512d60cdd30 HTTP/2
> Host: api.sandbox.push.apple.com
> User-Agent: curl/7.54.0
> Accept: */*
> apns-topic: com.livecode.pushNotificationAppHttp2
> apns-push-type: alert
> authorization: bearer eyAiYWxnIjogIkVTMjU2hujgImtpZCI6ICI5Uzc5SjY4OTjhyiB9.eyAiaXNzIjogIktSNjQ5TlNHSFAiLCAiaWFoiuhgMTYxNTk4NzU5OSB9.MEUCICJ5C97K244rRLoLUyej1yPN4v1LU7BLuPeHe7dPzQeBAiEAtJsb5YNke93QVBxJCUY-5jsNJ60-bhooEjzw2_GZatY
> Content-Length: 74
> Content-Type: application/x-www-form-urlencoded
> 
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
* We are completely uploaded and fine
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
< HTTP/2 200 
< apns-id: 96FCFEF1-93EF-1086-A5D3-22UHYGA9DFAA
< 
* Connection #0 to host api.sandbox.push.apple.com left intact
panoss-MBP:~ panos$ 

After successfully running the script, you should see a notification in your device, displaying the alert part of the json data that is sent.

Tapping on the notification will open the app, and the pushNotificationReceived message will be sent to the stack, with the payload part of the json data:

Sending Push Notifications using LiveCode

Here is a sample stack, which you can use to send push notifications. You just need to fill in the details for:

  • Team ID
  • Signing Key (P8 Certificate)
  • Private Key ID
  • Bundle Identifier
  • Device Token
  • APNs Hostname

Then fill in the actual data you want to send, and click on the Send button:

Note: There are several third party apps you can use to send push notifications, for example the PushNotifications utility.

 

Also, in a production environment, you should use

APNS_HOST_NAME=api.push.apple.com

instead of

APNS_HOST_NAME=api.sandbox.push.apple.com

The signing key you have created in the Apple Developer portal will be the same for both development and production environment.

For each notification, you provide a payload with app-specific information and details about how to deliver the notification to the user. The payload is a JSON dictionary object (as defined by RFC 4627) that you create on your server. The JSON dictionary object must contain an aps key. For more information, see here

Troubleshooting Common Issues

Here are a couple problems you might encounter:

No notifications arrive at the device: Make sure you have allowed the app to receive push notifications. You can check that in the Settings app. Also make sure you are connected to the Internet and the device clock is set to the correct date and time.

Some notifications arrive, but not all: If you’re sending many push notifications simultaneously but you receive only a few, this is expected. There is a queue of size 1, so if you send multiple notifications, the last notification is overridden.

Problem connecting to Push Notification Service: One possibility could be that there is a firewall blocking the ports used by APNs. Make sure you unblock these ports. Also, make sure the device is connected to the Internet, and the device clock is set to the correct date and time.

28 Comments

simon

Hi team/Panos, many thanks for this excellent tutorial. It worked like a charm for me (except the sh part but probably I did something wrong). I'd like to know how can I add the badge number (the little red circle with the number) and also the Icon to be shown on the Alert. Many thanks in advance

Panos Merakos

Hello Simon,

To add the badge number, you just have to include something like e.g.

"badge":5

in the JSON data, in the "aps" key. For example, in the sample stack, change this:

{
"aps":{"alert":"You have a new message"},
"payload":"Hello from LiveCode"
}

to this:

{
"aps":{"alert":"You have a new message","badge":5},
"payload":"Hello from LiveCode"
}

RE how to add the Icon to be shown on the alert, I need to investigate.

Were you able to add the icon previously (i.e. before the migration to http/2 push notifications)?

Kind regards,
Panos
--

Simon

Panos, many thanks for your prompt and precise answer. Badge is showing as expected and please disregard the icon question, there is nothing to be done because iOS takes care of displaying the APP's icon.

Regards

Panos Merakos

Thanks for the update, Simon.

simon

For those who may be interested to play a sound when a notification arrives:
"sound":"ON"
has to be included in the JSON data, in the "aps" key, like this

"aps":{"alert":"You have a new message","badge":5,"sound":"ON"},

Trevix

Hello. I am on OSX 10.15.7, Xcode 12.4, LC 9.6.3RC3, iOS 14.7.1 and iOS 14.4.2
I have a "test stack", for push notifications, the works just fine, registering on launch on iPhone.
This Test stack uses the same Provisioning profile and internal App ID of my real standalone, that refuses to register for push notification on launch.
Thinking of some code problem on launch or incompatible inclusions on the setting, I removed all the code from PreOpenStack (and open stack) and most inclusions on the Standalone settings, so that the App now opens and just stay there, without doing anything.
Still, the "pushNotificationRegistered", that is in the main stack script never gets fired.
Is it possible that there is something in the pList that interferes? Some other reasons?
Note that the same standalone, on Android, receives the "pushNotificationRegistered", so it is only a iOS problem.
I also tried to repeat this lesson, with all new data, on the standalone, to no result. On the test stack instead they work.
Thanks for any help or suggestion

Panos Merakos

Hello Trevix,

A rough guess is that the name of the "real" standalone (in the iOS settings) has accented characters. I vaguely remember a similar issue in the past, affecting iOS 14, where if the standalone name contained accented characters, the pushNotificationRegistered msg was not sent.

Is this the case by any chance?

Kind regards,
Panos
--

Trevix

No. The name of the standalone is "Segnapunto".
Searching on the forum for a solution, I also tried removing the splash image and also putting a "pushNotificationRegistrationError" (that doesn't get called).
I also tried removing the "answer pMessage" (used to understand what was happening) from the pushNotificationRegistered, replacing it with with a "put pMessage on old xx".
The test stack works on both iOS versions (an iphone and an iPad) while the standalone doesn't.
I guess that, under the wood, at launch the App notify something to the iOS: is there anyway to intercept what happens?

Panos Merakos

Thanks for the follow up, Trevix. Hmm interesting. I think the best way to troubleshoot this is to send both stacks (the test one and the "real" one) to [email protected], so that we can have a closer look.

Kind regards,
Panos
--

Trevix

SOLVED: Thanks Panos. As always you are so helpful.
I had a custom pList that didn't fit.
I had to rebuild the custom pList from scratch, as from lesson, starting from the original LC generated one.

Regards
Trevix

Matthias Rebbe

Hi. Thanks for this very helpful tutorial. Your sample stack for sending out the push notifications uses currently a shell script for the sending process. Is it not possible to just use LC with tsNET?

Panos Merakos

Hello Matthias,

I chose to write the shell script since it was easier for me to wrap the suggested curl command into a shell command. But I guess it should be possible to use tsNet for that. In fact it would be even better to have an example of doing this with tsNet, since people could use this stack not only on Mac (as this is the case with this stack, since it uses sh) but also on Windows.

So if you have a working example on how to do this with tsNet, it is very welcome :)

Kind regards,
Panos
--

Matthias Rebbe

>>So if you have a working example on how to do this with tsNet, it is very welcome :)<<
I was afraid you would write something like that... ;)

After i wrote my comment here yesterday i started with converting all parts of the script that create the JWT variables to Livecode syntax.
I am not sure if it's possible to create the JWT_SIGNED_HEADER_CLAIMS just with LC or if openssl is needed for that part. So at least that part needs to be done using the shell. As openssl is also available for Windows that would not be a problem. What do you think.

First I will try to use the LC created variables to create the JWT_SIGNED_HEADER_CLAIMS variable and the part of curl. If that is successfull i will try to do something with tsNET.

Simon

Hi Panos, here I'm again fighting with Apple's Certificates, Identifiers & Profiles.
I have an APP on the store that uses the legacy Push Notification protocol deprecated in April 2021 which I want to update to the new HTTP/2 based network protocol. How do I have to proceed in order to generate the new environment? In other words do I have to remove the original ID and create a new one using the same Bundle ID? If I do so will the installed base have any impact? On the other hand if I try to edit the original ID I don't see the possibility to "Register" it as described in this lesson.
Hope I made myself clear and many thanks in advance. I guess there should be more developers facing the same situation so your answer should be of great help not only for me.

Panos Merakos

Hello Simon,

Since in the original App ID entry you have enabled "Push Notifications" in the capabilities section, I do not think you need to do anything else regarding the app ID. You just have to generate the key (in the keys section), and use it to send a notification to this app, according to this lesson.

Cheers,
Panos
--

Simon

Thanks Panos, Do I have to create a new Certificate? There is no mention to the Certificates in the lesson.
Sorry for bothering you so much...

simon

Hi Panos, I'm getting the following message in the "output" field. Could you please help me understand what am I'm doing wrong this time? Many thanks

Panos Merakos

Hello Simon,

Nope, you won't need to create a new certificate. What has now changed is the way you send the push notification, not the way the app receives it. Previously you needed 2 kinds of certificates: one for signing the app, as usual, and this remains the same, and one for sending the push notification. The latter one has been replaced by the "key".

Hope this helps.

Panos Merakos

Hello Simon,

I do not see the message in the output field you were referring to in your previous comment. Could you please re-send it?

Cheers,
Panos

Simon

Hi Panos, you don't see the message because I didn't add it :-)
Sorry. Anyway many thanks for all your clarifications and help. I got it working now (at least in the development environment) !
Regards

Panos Merakos

Yay :)

You're welcome - have a great holiday :)

Cheers

Simon

Hi Panos, I'm switching from the development to the production environment and when testing the Testflight version everything seems to be OK but the notification doesn't reach the device. Strange thing is that it worked fine two times (one with the APP closed and one with the APP running) and then doesn't any more. Below you can see the shell response after running your script and I'm confident you are going to get some useful hints from it. Looking forward for your valuable hints, regards.

% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed

0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 17.188.130.153...
* TCP_NODELAY set
* Connected to api.push.apple.com (17.188.130.153) port 443 (#0)

0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
} [232 bytes data]
* TLSv1.2 (IN), TLS handshake, Server hello (2):
{ [100 bytes data]
* TLSv1.2 (IN), TLS handshake, Certificate (11):
{ [4395 bytes data]
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
{ [300 bytes data]
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
{ [919 bytes data]
* TLSv1.2 (IN), TLS handshake, Server finished (14):
{ [4 bytes data]
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
} [7 bytes data]
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
} [37 bytes data]
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.2 (OUT), TLS handshake, Finished (20):
} [16 bytes data]
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
{ [1 bytes data]
* TLSv1.2 (IN), TLS handshake, Finished (20):
{ [16 bytes data]
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use h2
* Server certificate:
* subject: CN=api.push.apple.com; OU=management:idms.group.887777; O=Apple Inc.; ST=California; C=US
* start date: Dec 9 03:25:59 2021 GMT
* expire date: Jan 8 03:25:58 2023 GMT
* subjectAltName: host "api.push.apple.com" matched cert's "api.push.apple.com"
* issuer: CN=Apple Public Server RSA CA 12 - G1; O=Apple Inc.; ST=California; C=US
* SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fa7d780dc00)
> POST /3/device/here goes de device token HTTP/2
> Host: api.push.apple.com
> User-Agent: curl/7.64.1
> Accept: */*
> apns-topic: com.luckro.luckrospot
> apns-push-type: alert
> authorization: bearer eyAiYWxnIjogIkVTMjU2IiwgImtpZCI6ICIyM0c3MlFRSkgzIiB9.eyAiaXNzIjogIjNGNzQ1WDI1NFAiLCAiaWF0IjogMTY0NzYyMTYwMyB9.MEUCICpJC3VsJb5d8Oj3EpC941W3POXKCGs-55EyCKAJ6MoTAiEApogYzvQ0OP40PwUdqnnv3Ze3Wa-Y1VGsKSuNWL1ZabU
> Content-Length: 93
> Content-Type: application/x-www-form-urlencoded
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 1)!
} [93 bytes data]
* We are completely uploaded and fine
* Connection state changed (MAX_CONCURRENT_STREAMS == 1000)!
< HTTP/2 200
< apns-id: B9AB0735-2CCA-15EF-009C-7E0947A1B54E
<
{ [0 bytes data]

100 93 0 0 100 93 0 81 0:00:01 0:00:01 --:--:-- 81
* Connection #0 to host api.push.apple.com left intact
* Closing connection 0

Simon

Hi Panos, please disregard my previous message (again...). I found the reason why it wasn't working, I had accented characters both on the Alert and in the Payload fields, once removed (or UTF-8 encoded) then it works fine. As a suggestion I guess it should be mention in the lesson to be aware with the accents, they bite!!!

Panos Merakos

Hello Simon,

Heh, I was just about to respond and ask you to check for accented characters in the standalone name (see https://quality.livecode.com/show_bug.cgi?id=22912). But it seems accented characters in the Alert and Payload fields cause an issue as well - good to know :)

Kind regards,
Panos
--

Ralph

Hey!
Just got to iOS today and a couple of items. Again thanks to both of you for sorting most of this out. Works like a charm!
1) The spaces in the device identifier from either the registration or mobileGetDeviceToken must be removed. This bit me for an hour but no biggie. Of course I removed the brackets,
2 This might be an iOS characteristic? You lose the pushNotificationReceived message if you swipe the app off to close it, send a notification and click on the notification. The app opens but no message. Reboot and it works or re-install the app and it works. App must still be active is some way even if you swipe it off.
3) The last item. After using the "Apple purchase notification message" and dealing with JWT. The shell script looked suspiciously familiar. Your are doing the opposite of what I was doing last week, creating the 3 period delimited items. Is this possible in LCS? I need to have Win users send the notifications. I guess I can write an web API on the on-rev.com server to run the shell script or do it in LCS. The base 64 stuff with the 2 required substitutions in LCS is easy but it's the "openssl dgst -binary -sha256 -sign" that I don't have a handle on. Any suggestions?
Thanks

Panos Merakos

Hello Ralph,

1. Yes, the Device ID string does contain spaces, which you have to remove.
2. Hmm, does it sound like this bug? https://quality.livecode.com/show_bug.cgi?id=10901
3. I believe it should be possible on Windows using tsNet, once this issue is fixed: https://quality.livecode.com/show_bug.cgi?id=23355

Otherwise, your idea of writing an web API on the on-rev.com server to run the shell script sounds like a good approach until bug 23355 is fixed.

Cheers,
Panos
--

Ralph

Panos,
Thanks for the info! Any time frame for a tsNet update?
Also, if you want to send you notification text with some structure you can substitute the alert string with
{"title" : "Your title",
"subtitle" : "Your Subtitle",
"body" : "Your body text"}

This format is preferred by Apple.

Matthias Rebbe

@Ralph, according to a reply i received to a support request about this, the fix is planned to be included in LC 9.6.8.

Add your comment

E-Mail me when someone replies to this comment

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.