← Back to list

Typescript: Remote Notification Server With Certificate-Based Connection For APNs

Start From the Key generation! Use HTTP2! Without any 3rd party libraries!

Itsuki in JavaScript in Plain English · 2025-09-21 09:54 · 2 claps · 7.5 min read
#typescript #apps #apns-certificate #push-notification #push-notification-service
Open on Medium ↗
Wiki topics: 🌐 · Web Development

Typescript: Remote Notification Server With Certificate-Based Connection For APNs

Start From the Key generation! Use HTTP2! Without any 3rd party libraries!

I have always been using AWS SNS to send push notifications but I decided I want the server myself!

This can be especially useful when testing locally!

So!

In this article, we will be

  • Starting with a quick overview of the different authentication types to APNs available, token-based or certificate-based, and some of the use cases for each approach
  • Creating some keys and generating some certificates
  • Making certificate-based connection to APNs with Typescript and http2, and sending some notifications!

I will also be sharing with you some of the Errors I ran into and the solutions to those!

Let’s start!

Connection Types Overview

To send remote notifications, our provider server must establish either token-based or certificate-based trust connection with APNs using HTTP/2 and TLS.

Here are some of the main advantages for each of the techniques in authenticating.

Token-based

  1. Stateless. Which means faster than certificate-based communication because it doesn’t require APNs to look up the certificate, or other information, related to your provider server.
  2. We can use the same token from multiple provider servers.
  3. We can use the same token for multiple different apps

certificate-based

  1. Establish trust between our server and APNs at the server level which means individual notification requests contain only your payload and a device token and therefore reduces the size of each notification request.
  2. We can use it to send notifications to a single app to specific services such as background VoIP associated with a single app.

🚨 Important!

Certificates must be tied to a specific app.

Certificate-based Authentication Flow

As I have mentioned, In this article, we will be making our provider server certificate-based!

So! Let’s just get an idea of the general flow of the connection.

media-4286315~dark@2x.png

media-4286315~dark@2x.png

  1. We initiate a connection to the APNs server with the provider certificate we obtained from Apple using Transport Layer Security (TLS)
  2. APNs responds by sending a certificate for our server to validate
  3. We validate it using the private key of the certificate. Or actually we will not be performing any validation algorithm by ourselves because the awesome http2 can handle that for us!
  4. After validation, connection is established and we can begin sending remote notification requests to APNs.

Little Note here!

Communication between the server and APNs must take place over a secure connection which means we will need to install the ***AAA Certificate Services root certificate and [SHA-2 Root : USERTrust RSA Certification Authority certificate](https://www.sectigo.com/knowledge-base/detail/Sectigo-Intermediate-Certificates/kA01N000000rfBO)*** on our server.

An up-to-date version of macOS (And that is me here!) includes the appropriate trust store to validate the certificate presented by APNs.

So I will not be doing anything extra!

But!

On other systems, you might need to install this certificate yourself. You can download the ***AAACertificateServices 5/12/2020 certificate and [SHA-2 Root : USERTrust RSA Certification Authority](https://www.sectigo.com/knowledge-base/detail/Sectigo-Intermediate-Certificates/kA01N000000rfBO) certificate from the Sectigo KnowledgeBase***.

Generate Those Keys & Certificates

Honestly speaking, this is probably the part that I ended up spending most of my time on!

Because the key formats! What do I meant by that? We will see!

Create a Certificate Signing Request

To have Apple generate a certificate, we will first need to create an CSR (Certificate Signing Request).

You can choose to generate it in a way you like, but I will be using Keychain Access here. This will make our life a little easier later when trying to retrieve the private key.

Choose Keychain Access > Certificate Assistant > Request a Certificate from a Certificate Authority.

In the Certificate Assistant dialog,

  1. enter an email address in the User Email Address field.
  2. In the Common Name field, enter a name for the key (for example, Gita Kumar Dev Key).
  3. Leave the CA Email Address field empty.
  4. Choose Saved to disk
  5. Choose Continue to generate it!

Obtain A Certificate From Apple

  1. Sign in to ***developer.apple.com and select [Identifiers](https://developer.apple.com/account/resources/identifiers/list) under Certificates, IDs & Profiles***.
  2. Select the App ID (also known as Bundle ID) of our app and click Continue.

PS: You can also choose Certificates > Apple Push Notification service SSL (Sandbox & Production), and then select the App ID you want to . It will be the same!

  1. Scroll down to the Push Notifications section and choose Configure.

  1. Under Development SSL Certificate, choose Create Certificate. (Unless you want a production one!)

  1. Within the dialog popped up, upload the CSR we created above, create the certificate and download it.

You should now have something like aps_development.cer saved somewhere in your PC!

Get The Private Key

Let’s head back to the KeyChain Access.

Under Login > All items, we should see the private key linked to the CSR we have just created.

Right click and it and choose Export.

The only format we can choose from is p12 so enter some password and save it somewhere!

Key Format Conversion

We will actually need to convert the key format for both the certificate we obtained from Apple, as well as this p12 private key! To PEM! (You could use p12 with http2, but using a PEM instead will be a little simpler!)

Convert Certificate

The .cer file we have obtained from Apple is actually in DER format. If we pass it in directly to http2, we will get this Error: error:0480006C:PEM routines::no start line.

openssl x509 -in aps_development.cer -inform DER -out certificate.pem -outform PEM

Our new certificate.pem should look something like following.

-----BEGIN CERTIFICATE-----
MIIGvTCCBaWgAwIBAgIQewW9bwj+DCEUb+OFqUhHwzANBgkqhkiG9w0BAQsFADB1
// ...
Qg==
-----END CERTIFICATE-----

Convert Private Key

openssl pkcs12 -in privateKey.p12  -out privateKey.pem -nocerts -legacy -nodes

You will be prompt to enter the password you set while exporting the key.

Enter it and we should get something like following.

Bag Attributes
    friendlyName: // ...
    localKeyID: // ...
Key Attributes: <No Attributes>
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCJuXR98m5UK9V
// ...
g9hJRE0zrQflc6IWgWzSIsgO
-----END PRIVATE KEY-----

Note that we have passed in the -legacy option while converting?

Otherwise, we will get this inner_evp_generic_fetch:unsupported error!

Error outputting keys and certificates
008C7CF601000000:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:355:Global default library context, Algorithm (RC2-40-CBC : 0), Properties ()

Server Time!

We can finally start writing some code!

But before that, let’s just add the keys we have created and converted above to our server!

Establish Connection

First thing first!

Let’s use HTTP/2 and TLS 1.2 or later to establish a connection (or more than one to improve performance) between our server and one of the following APNs servers:

Since we (or at least I) have created the certificate for development, we will be connecting to the development server here!

import * as http2 from 'http2'
import * as fs from 'fs'

const DEV_APN_SERVER = "https://api.sandbox.push.apple.com:443"
// certificat file generated by Apple in PEM format
const CERT_PATH = './cert/certificate.pem'
// Certificate Signing Request (CSR) used for generating the certificate in PEM format
const PRIVATE_KEY_PATH = './cert/privateKey.pem'

const options: http2.SecureClientSessionOptions = {
    cert: fs.readFileSync(CERT_PATH).toString('utf-8').split(String.raw`\n`).join('\n'),
    key: fs.readFileSync(PRIVATE_KEY_PATH).toString('utf-8').split(String.raw`\n`).join('\n'),
}

// Connect to the HTTP/2 server
const client = http2.connect(DEV_APN_SERVER, options)

Little note here!

Make sure to add the https:// scheme to the server URL as it is required by the http2!

Send Notifications

Let me first share the code with you here and then point out the important points!

// send notification
const BUNDLE_ID = "your app's bundle id"
const deviceToken = "device token to send Push notification to"
// Example payload
const postBody = JSON.stringify({ "aps": { "alert": "Hello" } })

const req = client.request({
    ':method': 'POST',
    ':path': `/3/device/${deviceToken}`,
    "apns-push-type": "alert",
    "apns-expiration": 0,
    "apns-priority": 10,
    "apns-topic": `${BUNDLE_ID}`,
})

req.setEncoding('utf8')

let responseData = ''

req.on('connection', (stream) => {
    console.log('someone connected!')
    console.log(stream)
})

req.on('response', (headers, flags) => {
    console.log('Status:', headers[':status']) // Log the response status
})

req.on('data', chunk => {
    // Accumulate response data
    responseData = responseData + chunk
})

req.on('end', () => {
    console.log('Response:', responseData) // Log the complete response body
    client.close() // Close the client connection
})

req.write(postBody) // Write the POST body to the request stream
req.end() // End the request stream to send the request

Everything are just some simple POST requests with http2, except for this one point to pay attention to!

DO NOT include content-type or content-length headers!

Normally speaking, if we are to POST some JSON body, we will have something like following.

const buffer = Buffer.from(JSON.stringify(postBody))
// ...

const req = client.request({
    // ...
    'content-type': 'application/json',
    'content-length': buffer.length, // Set content-length for POST body
})

However, in the case of sending it to APNs server, I will end up with a NGHTTP2_PROTOCOL_ERROR!

[ERR_HTTP2_STREAM_ERROR]: Stream closed with error code NGHTTP2_PROTOCOL_ERROR

By the way, if you want to reuse the client to send other notifications, keep the connection open by commenting out the client.close() within the req.on('end',…).

Final But Important Note!

Yes! Above is all we have for the actual server! We can now send remote push notifications to our users!

Here is a really important note I would like to make to finish this article up!

Please! Like Seriously!

Check the expiration data of a certificate! For example, with Keychain Access.

Provider certificates are valid for a year and we must update them to continue communicating with APNs. Therefore, to avoid a disruption in the service, please update those certificates before they expire!

Some Useful Related Resources

Thank you for reading!

That’s it for this article!

Happy pushing notifications!

A message from our Founder

Hey, Sunil here. I wanted to take a moment to thank you for reading until the end and for being a part of this community.

Did you know that our team run these publications as a volunteer effort to over 3.5m monthly readers? We don’t receive any funding, we do this to support the community. ❤️

If you want to show some love, please take a moment to follow me on LinkedIn, TikTok, **Instagram. You can also subscribe to our [weekly newsletter](https://newsletter.plainenglish.io/)**.

And before you go, don’t forget to clap and follow the writer️!


메타데이터
post_id
cd8cc5ec092f
slug
typescript-remote-notification-server-with-certificate-based-connection-for-apns-cd8cc5ec092f
url
https://javascript.plainenglish.io/typescript-remote-notification-server-with-certificate-based-connection-for-apns-cd8cc5ec092f
canonical_url
https://javascript.plainenglish.io/typescript-remote-notification-server-with-certificate-based-connection-for-apns-cd8cc5ec092f
author_url
https://medium.com/@itsuki.enjoy
status
ok
fetched_at
2026-07-22 10:05:19