PHP Logo

The Web Push API lets you send push notifications to web browsers and APIs. While most of the logic happens in the browser, you still need a server-side component to generate your notifications. Here’s how to implement a Web Push backend using PHP.


For the purposes of this tutorial, we’ll assume you’re familiar with the basics of creating HTTP APIs in PHP. You’ll need to expose a few public endpoints using your web framework. These will be called by your in-browser JavaScript to register and unregister devices.

This article won’t touch on the browser-side code or how it works. You’ll need to put together a service worker that responds to incoming push events and displays a notification to the user.

At a high-level, the Web Push flow looks like this:

  1. A push subscription is registered in the browser. The browser issues a unique endpoint URL to your JavaScript.
  2. Your JavaScript sends the subscription data to your server and identifies the user it applies to.
  3. When your backend needs to send a push notification, create a payload and send it to the endpoint URL reported as part of the subscription data.
  4. The user’s browser will receive the payload via the vendor’s notification delivery platform. Your JavaScript service worker handles the consequent event and uses the browser’s notification API to alert the user.

Here’s how to implement the server-side aspects of steps 1 to 3.

Getting Setup

We’ll use the web-push Packagist package by minishlink. This abstracts the interactions with each browser notification platform so you don’t have to manually distinguish between endpoint types.

Add the package to your project using Composer:

composer require minishlink/web-push

To use the latest version, you need PHP 7.2 or greater with the gmp, mbstring, curl, and openssl extensions. If you must use an older PHP release, lock the package to an earlier version to maintain compatibility.

The library exposes a core WebPush class with methods that let you send notifications individually or as batches. Subscriptions are represented by instances of the Subscription class.

Providing VAPID Keys

Trust in the standards-compliant Web Push ecosystem is enforced through the use of VAPID keys. Your server needs a VAPID key pair so it can authenticate itself to browsers. The public key should be exposed via an API endpoint.

You can generate a VAPID key set using the web-push package:

use MinishlinkWebPushVAPID;
$keyset = VAPID::createVapidKeys();
// public key - this needs to be accessible via an API endpoint
echo $keyset["publicKey"];
// private key - never expose this!
echo $keyset["privateKey"];
file_put_contents("vapid.json", json_encode($keyset));

Generate keys for your system and store them to a persistent location. Add an API endpoint so your client-side JavaScript can retrieve the public key. This will be used to setup the browser’s push subscription. The user’s device will accept incoming push events if they’ve been signed using the corresponding VAPID private key.

Registering Push Subscriptions

The next step in the sequence is receiving push subscription requests from your clients. Once the browser’s confirmed a new push subscription, your JavaScript should send the subscription’s endpoint URL and associated authentication keys to your server. Store these details alongside the user’s ID so you can retrieve all the push-enrolled devices linked to the user later on.

We’re omitting code samples for this step as the implementation depends on your data storage layer and the values your JavaScript sends up. Typically, this will be a JSON representation of a PushSubscription object. You need a simple set of database-backed CRUD API endpoints to create a subscription, replace an existing one, and request a deletion when the user unsubscribes.

Preparing Subscriptions

Once a client’s successfully registered you can start sending notifications using the web-push library. Begin by creating an instance of the WebPush class:

use MinishlinkWebPushWebPush;
$webPush = new WebPush([
    "VAPID" => [
        "subject" => "",
        "publicKey" => "VAPID_Public_Key_Here",
        "privateKey" => "VAPID_Private_Key_Here"

You can reuse one WebPush instance each time you send a notification. The library needs to be configured with the VAPID key set you generated earlier. Keys should be encoded as Base64 but this is handled for you if you create them with the library.

The VAPID subject is used to identify your server and its contact details. You can supply a website URL or a mailto: email address link.

Next you need to retrieve the push subscription you’ll be sending to. Use your data access system to lookup the push endpoint URLs associated with the user you want to send to. Convert each subscription to a Subscription instance:

use MinishlinkWebPushSubscription;
// Get user's push data...
// SELECT * FROM push_subscriptions WHERE user_id = 123456
$subscription = Subscription::create([
    "endpoint" => "",
    "contentEncoding" => "aesgcm",
    "authToken" => "<auth token from JavaScript PushSubscription object>"
    "keys" => [
        "auth" => "<auth token from JavaScript PushSubscription object>",
        "p256dh" => "<p256dh token from JavaScript PushSubscription object>"

The auth property of the PushSubscription is repeated twice to cope with two different versions of the spec used by browser services. The P256DH property is another public key which should be supplied when set on the subscription.

The web-push library is compatible with Chrome and Firefox push endpoints. It’ll also work with any other Web Push implementation that meets the current standard.

Sending a Notification

Now combine your WebPush and Subscription instances to send a notification:

$result = $webPush -> sendOneNotification(
        "message" => "Demo notification",
        "foo" => "bar"

Calling sendOneNotification() provides immediate delivery for a single notification. The payload in this case is a JSON-encoded array with two properties. It’s up to you what data you send and the format you use – your JavaScript client receives it as-is and can interpret it as necessary.

Sending a notification returns a result class that lets you check whether the operation succeeded:

if ($result -> isSuccess()) {
    // all good
else {
    // something went wrong
    error_log($result -> getReason());
    // provides raw HTTP response data
    error_log($result -> getResponse());

You can take action to retry or cancel the delivery if an error occurs.

Notification subscriptions can also expire. Call the isSubscriptionExpired() method on a result class to determine whether this is the reason for the failure. You could delete the subscription from your database in this scenario, ensuring you don’t send anything else to a dead endpoint.

Batching Notifications

Notifications can be batched together for delivery with one method call:

$webPush -> queueNotification($subscription, ["msg" => "first"]);
$webPush -> queueNotification($subscription, ["msg" => "second"]);
foreach ($webPush -> flush() as $i => $result) {
    echo ("Notification $i was " . ($result -> isSuccess() ? "sent" : "not sent"));

This is useful when you know you’ll be sending a large number of notifications in a short timeframe. Queue all your payloads and let web-push deliver them in the optimal way.

You can limit the number of notifications sent in a single flush() by passing an integer to the method:

$webPush -> flush(100);     // send 100 messages

The default value is 1000.

Notification Options

sendOneNotification() and queueNotification() accept the following options as a third array argument:

  • TTL – Controls how long the browser’s notification platform will hold onto the notification if it can’t be passed to the user’s device immediately. If the user’s device is offline, platforms default to trying to deliver it for the next four weeks. If you’re sending a notification that won’t be relevant next week, adjust the TTL accordingly so the user doesn’t see outdated content.
  • urgency – Accepts normal, low or very-low as values. Some platforms may use this to adjust the frequency of notification delivery. Devices that enter a battery saving mode may suspend delivery of non-urgent notifications.
  • batchSize – This has the same effect as the argument to flush() described above.

You can configure default option values using the second argument to the WebPush constructor:

$webPush = new WebPush(["VAPID" => [...]], ["TTL" => 3600]);


The web-push library makes it easy to send Web Push notifications using PHP. You get an abstraction layer atop the various browser platforms that supports batching, error handling, and all Web Push features.

The Web Push mechanism is an unusual browser system as it’s reliant on remote server-side components you supply yourself. This can make it seem opaque and technical. In practice, creating a simple PHP backend is quick and easy; the frontend implementation is usually the more time-consuming aspect, particularly if you’re not already using service worker features.

Profile Photo for James Walker James Walker
James Walker is a contributor to How-To Geek DevOps. He is the founder of Heron Web, a UK-based digital agency providing bespoke software development services to SMEs. He has experience managing complete end-to-end web development workflows, using technologies including Linux, GitLab, Docker, and Kubernetes.
Read Full Bio »