Send a push message via APNs with a PHP Script
HTTP/2 is the new standard (and from November 2020 onwards the only way to connect to the APNS) in this article i’ll explain how to use PHP to connect to APNS using JWT token (recommended) or by using certificates.
Apple’s service to send push notifications to iPhones, iPads and even to Apple’s watches is called APNS: Apple Push Notification Service) .
In order to send Push Notifications you’ll need an IOS app and a server to send the message from. In this article I will provide information on how to send the messages via PHP using a library to make it easier to generate the required JWT token.
What do you need in order to send Push Notifications via APNS?
The delivery of remote notifications involves several key components. We’ll be needing a receiver (iPad, iPhone, ect) and a server to send the message from (provider server)
- Your company’s/personal server (known as the provider server) -- we’ll be using PHP on our provider server
- Apple Push Notification service (APNs) -- Private key (or certificate) from Apple (.p8 file)
- The user’s (apple) device (iPad, iPhone, ect.)
- IOS application (Your app running on the user’s device) -- for example: nl.samauto.ios-application
Getting the private key from Apple
Apple Push Notification service (APNs) must know the address of a user’s device before it can send notifications to that device. The address takes the form of a device token unique to both the device and your app ( nl.samauto.ios-application ) . At launch time, your app communicates with APNs and receives its device token, which you then forward to your provider server (the one we are going to build with PHP). Our server will include that token with any notifications it sends to Apple.
Head over to Developer.apple.com and login with your AppleId.
Go to “Certificates, Identifiers & Profiles > Keys“
Generate a new key for your application, make sure to enable the Push Notifications Capability.
Download the .p8 file and save it to a secure location.
IT CANNOT BE DOWNLOADED AGAIN (APPLE DELETES THE PRIVATE KEY)
Let's build the PHP Server!
We’ll be building our server with PHP.
To authenticate to APNS we are utilizing JWT (JSON Web Tokens – RFC 7519) to create a Token Based Authenticated connection to Apple’s servers.
We will use composer to add a package (jwt) developed by Luís Cobucci. lcobucci/jwt on GitHub or at packagist.org
To add the package (via composer) to the project use the following command:
composer require lcobucci/jwt
We can use the following script:
<?php
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Ecdsa\Sha256;
use Lcobucci\JWT\Configuration;
$config = $container->get(Configuration::class);
assert($config instanceof Configuration);
$device_token = "device_token_here";
$apns_topic = 'to.dev.ios-application';
$p8file = "/home/dave/samauto/key_from_apple.p8";
$token = (string) $config->createBuilder()
->issuedBy("DEF123GHIJ") // (iss claim) // teamId
->issuedAt(time()) // time the token was issuedAt
->withHeader('kid', "ABC123DEFG")
->setKey('file://' . $p8file)
->setSigner(new Sha256()) // APNs only supports the ES256 algorithm
->getToken(); // get the generated token
$payloadArray['aps'] = [
'alert' => [
'title' => "Dev.To Push Notification", // title of the notification
'body' => "Visit SamAuto.nl for more awesome scripts", // content/body of the notification
],
'sound' => 'default',
'badge' => 1
];
$payloadJSON = json_encode($payloadArray);
$url = "https://api.sandbox.push.apple.com/3/device/$device_token";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payloadJSON);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $token","apns-topic: $apns_topic"]);
$response = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// On successful response you should get true in the response and a status code of 200
// A list of responses and status codes is available at
// https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/TheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH107-SW1
var_dump($response);
var_dump($httpcode);
I will explain the script in steps.
First we'll need a deviceId from a IOS device, the application identifier of your application installed on the IOS device and the filePath to the .p8 file.
// replace the device token with "device_token_here"
$device_token = "device_token_here";
// use your IOS application ID
$apns_topic = 'to.dev.ios-application';
// replace $p8file with the location to your .p8 file you downloaded from Apple.
$p8file = "/home/dave/samauto/key_from_apple.p8";
Next we will use the JWT library to generate a JWT token to authenticate the HTTP/2 call to Apple's API.
// Replace "DEF123GHIJ" with your TeamId
// Replace "ABC123DEFG" with your (Encryption) KeyId
$token = (string) $config->createBuilder()
->issuedBy("DEF123GHIJ") // (iss claim) // teamId
->issuedAt(time()) // time the token was issuedAt
->withHeader('kid', "ABC123DEFG")
->setKey('file://' . $p8file)
->setSigner(new Sha256()) // APNs only supports the ES256 algorithm
->getToken(); // get the generated token
// $token will now contain a JWT Token for example:
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
You can find out more about JWT tokens on JWT.IO
The next part of the script is creating a payload to send to apple and encode is as JSON.
$payloadArray['aps'] = [
'alert' => [
'title' => "Dev.To Push Notification", // title of the notification
'body' => "Visit SamAuto.nl for more awesome scripts", // content/body of the notification
],
'sound' => 'default',
'badge' => 1
];
$payloadJSON = json_encode($payloadArray);
The payload will look like this:
{
"aps": {
"alert": {
"title": "Dev.To Push Notification",
"body": "Visit SamAuto.nl for more awesome scripts"
},
"sound": "default",
"badge": 1
}
}
We will send that to Apple's Sandbox URL (https://api.sandbox.push.apple.com/)
And append "3/device/" and the value of $device_token
Next we will build a cURL request (make sure you have a HTTP/2 enabled version of CURL installed)
curl_setopt($ch, CURLOPT_POSTFIELDS, $payloadJSON);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $token","apns-topic: $apns_topic"]);
$response = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
If all went well you should see a 200 OK HTTP status code.
Happy Hacking!
Top comments (12)
If you're trying to get this to work in 2021:
missing container usage can be replaced by
And the builder signature changed to:
Just use GuzzleHttp\Client and we don't need JWT. See the working code-
$url = "api.sandbox.push.apple.com/3/device/";
$headers = array(
"apns-topic: com.example.exampleapp",
"apns-push-type: alert",
"Content-Type: application/x-www-form-urlencoded",
);
$certificate_file = "iosCertificates/apple-push-dev-certificate-with-key.pem";
$payloadArray['aps'] = [
'alert' => [
'title' => "Test Push Notification",
'body' => "Ohhh yeah working", ],
'sound' => 'default',
'badge' => 1
];
$data = json_encode($payloadArray);
$client = new Client();
$response = $client->post($url, [
'headers' => $headers,
'cert' => $certificate_file,
'curl' => [
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
],
'body'=> $data,
]);
This is the correct answer. I was able to use this to send Safari Web Push Notifications.
Ensure that you include the device token in the URL:
$url = "api.sandbox.push.apple.com/3/devic...", otherwise it won't work.
I tries this. Getting following error:
Fatal error: Uncaught GuzzleHttp\Exception\ConnectException: cURL error 7: Failed to connect to api.sandbox.push.apple.com port 80: Connection timed out
Am I missing anything here?
Where does $container come from?
lcobucci-jwt.readthedocs.io/en/lat...
The examples here fetch the configuration object from a hypothetical dependency injection container. You can create it in the same script or require it from a different file. It basically depends on how your system is bootstrapped.
Those who are having the same issue just like I had. Add this code in the curl code and it will work.
Don't forget there is a great package out there that can do this all: github.com/edamov/pushok
Where does the $container comes?
I am getting error with $container.
Did anyone get an answer as to where $container comes from? I get no response whatsoever back from this script. I guess none of this actually works.
I am getting 400 code on the server and 200 on my local end. How to fix it?