PWA Push Notifications on iOS in 2026: What Actually Works

Updated:
✦ Ask AI about this article
PWA Push Notifications on iOS in 2026: What Actually Works

Push notifications for PWAs on iOS have been one of the most discussed topics among web developers for the past two years. Apple kept this feature closed for a long time, and when they opened it, they did so with limitations that still raise questions in practice.

This article provides a technical breakdown based on my experience with two real-world projects: Kazki AI, a service for generating children's fairy tales from prompts (project now closed), and a practicing psychologist's blog, where push notifications informed subscribers about new courses and publications. What works, what doesn't, and what code is needed to make it all work on an iPhone.

If you are still choosing between a PWA and a native app, read PWA vs Native in 2026: Comparison and a Real Case Study first.

📚 Table of Contents

🎯 Section 1. Why iOS Didn't Support PWA Push Notifications for Years

Short answer: until iOS 16.4 (March 2023), web apps on iPhone could not receive push notifications at all — Apple simply did not implement Web Push for iOS, although Android had supported it for many years.

Android (Chrome) has supported Web Push since 2015. Apple only added support for iPhone and iPad in iOS 16.4 — eight years later. Apple officially did not explain the reasons. The most common hypothesis in the industry is business-related: PWAs with full push capabilities reduce the incentive to publish apps in the App Store, where Apple charges a 15–30% commission. But this is speculation, not a confirmed fact.

Before iOS 16.4, web apps on iPhone could not receive push notifications — Web Push was simply absent on the platform.

It wasn't a technical barrier: Safari on macOS had supported push notifications for websites since OS X Mavericks (2013), using APNs (Apple Push Notification service). However, this was Apple's own implementation, and the standard Web Push (Push API + VAPID) only appeared in Safari on macOS in version 16 (2022). It was on this basis that Apple built support for iOS. [WebKit: Web Push for Web Apps on iOS and iPadOS]

External pressure also played a role: regulatory scrutiny of the App Store in the EU and other jurisdictions gradually shifted Apple's stance on web apps. A notable episode occurred later, in February 2024: in the beta version of iOS 17.4 for EU users, Apple announced that it would not be able to support Home Screen web apps in their usual form due to the Digital Markets Act requirements — and after a wave of criticism from developers, it abandoned this decision before the release. This means Web Push for iOS appeared earlier and independently of this story. [Brainhub: PWA on iOS — Current Status]

🎯 Section 2. iOS 16.4, 17, 18: A Timeline of Changes — What Was Introduced and What's Still Broken

The key change happened once — in iOS 16.4. Subsequent versions did not alter the fundamental model, and the problem with "disappearing" subscriptions, according to developers, has not completely disappeared.

iOS 16.4 enabled Web Push for PWAs added to the home screen and allowed third-party browsers to offer "Add to Home Screen." iOS 17 and 18 did not introduce drastic changes to Web Push, but developers continue to report instances where push subscriptions "disappear" without an obvious reason.

iOS 16.4 is the start, not the end. The fundamental model (push only for installed PWAs) has not changed, and the stability of delivery on iOS, based on developer experience, still lags behind Android.

iOS 16.4 (March 2023) — The Launch

The first release with Web Push support for Home Screen web apps. Notifications appear on the Lock Screen, in the Notification Center, and on Apple Watch. Support for the Badging API was also added. In the same release, third-party browsers (Chrome, Edge, and others) gained the ability to offer users "Add to Home Screen" in the Share menu. [WebKit: Web Push for Web Apps on iOS and iPadOS]

Practice revealed a problem: developers reported that push subscriptions stopped working after 1–2 weeks. The endpoint became inactive, and the user had to reinstall the PWA to subscribe again. [Apple Developer Forums: PWA Push Issues iOS]

iOS 17 (September 2023) — No Changes in Web Push

In iOS 17, the fundamental Web Push model did not change: subscriptions are still only possible for web apps added to the home screen. Some developers noted improved stability, but complaints about disappearing subscriptions persisted. [OneSignal: iOS Web Push Setup]

iOS 17.4 (February–March 2024) — The EU Episode

In the beta version of iOS 17.4 for EU users, Apple announced the discontinuation of full Home Screen web apps (they would open as regular Safari tabs). After criticism from developers and regulators, this decision was reversed, and PWA support in the EU was retained.

iOS 18 (September 2024) — Stabilization

There were no significant changes to the Web Push API. Some developers are observing improvements in delivery reliability, while others are experiencing the same problems with lost subscriptions after prolonged app inactivity. [Apple Developer Forums: iOS 17–18 Discussion]

🎯 Section 3. The Main Condition: PWA Must Be Added to the Home Screen — Regardless of the Browser

Push notifications on iOS only work for web apps added to the home screen (Share → Add to Home Screen). An open tab — in Safari or any other browser — does not grant push subscription.

Unlike Android, where Chrome can request push permission directly in a tab, iOS requires the site to be installed as a separate app on the home screen. Starting with iOS 16.4, it can be added not only through Safari: third-party browsers can also offer "Add to Home Screen" in the Share menu. The result is the same — the site opens as a Home Screen web app and has the same push support. Important: the presence of the option in the menu depends on the specific browser, so in the instructions for the user, it's better to show the universal path "Share → Add to Home Screen."

Web Push on iOS is only available for Home Screen web apps: a site added to the home screen can request notification permission, but only in response to user action — for example, clicking a "Subscribe" button.

This means that before requesting notification permission, you need to check three things: if it's iOS, if PushManager is available, and if the app is open in standalone mode (i.e., installed on the home screen). Please note: iPads with iPadOS 13+ identify themselves as Mac in the user agent, so checking only for iphone|ipad is not enough.


function canUsePush() {
  const isIOS =
    /iphone|ipad|ipod/i.test(navigator.userAgent) ||
    (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1); // iPadOS

  const isStandalone =
    window.matchMedia('(display-mode: standalone)').matches ||
    window.navigator.standalone === true;

  if (isIOS && !isStandalone) {
    // Show hint: "Add to Home Screen to receive notifications"
    showInstallPrompt();
    return false;
  }

  return 'serviceWorker' in navigator && 'PushManager' in window;
}

If you skip this check, the push API will be unavailable in a regular tab on iOS (e.g., PushManager will be missing), and the subscription code will fail with an error. The user won't understand why the "subscribe" button isn't working — so the prompt to install should be shown in advance.

PWA Push Notifications on iOS in 2026: What Actually Works — 2

🎯 Section 4. Step-by-step: User Subscription to Push on iOS

iOS subscriptions require a user gesture — the permission request cannot be triggered automatically on page load, only in response to a user action.

On iOS, a web app added to the home screen can only request notification permission in response to direct interaction — for example, clicking a "Subscribe" button. Therefore, Notification.requestPermission() must be called directly from a click handler. Calls from setTimeout, DOMContentLoaded, or other automatic code will not work on iOS. On Android, such a request is technically possible without a click, but browsers are increasingly blocking it, so a trigger button is a universal practice. [WebKit: Web Push for Web Apps on iOS and iPadOS]

The push permission request on iOS is only accepted in response to a user action: automatic requests on page load do not work. [Apple WWDC22: Meet Web Push for Safari]

Here's the full subscription flow from a real Kazki AI project (we defined the canUsePush() function in the previous section):


const VAPID_PUBLIC_KEY = 'YOUR_PUBLIC_VAPID_KEY';

// Convert VAPID key (base64url) to Uint8Array for subscribe()
function urlBase64ToUint8Array(base64String) {
    const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
    const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
    const raw = atob(base64);
    return Uint8Array.from([...raw].map(ch => ch.charCodeAt(0)));
}

async function initPushSubscription() {
    // 1. Check for support and standalone mode on iOS (see Section 3)
    if (!canUsePush()) return;

    try {
        // 2. Request permission — the FIRST asynchronous action in the click handler,
        //    while the user gesture is still "alive". If permission is already granted —
        //    the method will return 'granted' immediately.
        const permission = await Notification.requestPermission();
        if (permission !== 'granted') return; // 'denied' — show a hint about settings

        // 3. Wait for the Service Worker
        const reg = await navigator.serviceWorker.ready;

        // 4. Get existing subscription or create a new one
        let subscription = await reg.pushManager.getSubscription();
        if (!subscription) {
            subscription = await reg.pushManager.subscribe({
                userVisibleOnly: true,
                applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
            });
        }

        // 5. Save (or update) the subscription on the server
        const { endpoint, keys } = subscription.toJSON();
        const response = await fetch('/api/push/subscribe', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                endpoint: endpoint,
                p256dh:   keys.p256dh,
                auth:     keys.auth
            })
        });
        if (!response.ok) throw new Error('Server rejected subscription: ' + response.status);

    } catch (e) {
        console.error('Push error:', e.message);
    }
}

// Subscribe only on click — not automatically!
document.querySelector('#push-btn')
        .addEventListener('click', initPushSubscription);

Note the order: requestPermission() is called before any await for other operations. Additionally, it's better to send the subscription to the server every time, even if it already exists — this way the backend always has the current endpoint.

The subscription endpoint on iOS typically starts with https://web.push.apple.com/, on Chrome on Android — with https://fcm.googleapis.com/. This is convenient for diagnostics, but you shouldn't hardcode logic based on it: the format might change. [pqvst.com: Demystifying Web Push Notifications]

🎯 Section 5. Web App Manifest and Service Worker: Required Settings

For Web Push on iOS, the site must be installed as a Home Screen web app, and the manifest must declare the display mode as standalone (or fullscreen). Without this, the Push API in your app will not work, even if everything else is configured correctly.

Apple considers a site whose manifest specifies a display mode other than a regular tab as a "Home Screen web app". If this is missing, PushManager is unavailable, and the subscription code will not execute. Practical takeaway: always specify "display": "standalone". [GitHub: webpush-ios-example]

Push on iOS is only available for sites added to the home screen — and a manifest with display: standalone is a practical requirement for such an app to function correctly.

Minimal manifest.json for Push on iOS:


{
  "name": "Kazki AI",
  "short_name": "Kazki",
  "display": "standalone",
  "start_url": "/",
  "scope": "/",
  "background_color": "#ffffff",
  "theme_color": "#6366f1",
  "icons": [
    {
      "src": "/images/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/images/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Icons must be actual, accessible PNG files of the correct size. Additionally, include <link rel="apple-touch-icon" href="/images/icon-192.png"> in the <head>: iOS traditionally relies on this. If there's no icon, iOS will use a fallback icon, and the app will look unprofessional on the home screen.

Service Worker: Why Authorization and API Cannot Be Cached

A common mistake is aggressive caching that breaks authorization. Here's the approach from Kazki AI: cache only static files from your domain (styles, scripts, images, fonts), and do not touch HTML pages, APIs, or authorization at all:


const CACHE_NAME = 'static-v1';
const STATIC_DESTINATIONS = ['style', 'script', 'image', 'font'];

self.addEventListener('fetch', event => {
    const request = event.request;
    const url = new URL(request.url);

    // Do not cache: non-GET, external domains, API
    if (request.method !== 'GET') return;
    if (url.origin !== self.location.origin) return;
    if (url.pathname.startsWith('/api/')) return;

    // HTML pages (login, dashboard, profile, etc.) always come from the network —
    // only cache static resources
    if (!STATIC_DESTINATIONS.includes(request.destination)) return;

    // Network-first, fallback to cache
    event.respondWith(
        fetch(request)
            .then(response => {
                if (response.ok) {
                    const clone = response.clone();
                    caches.open(CACHE_NAME)
                          .then(cache => cache.put(request, clone));
                }
                return response;
            })
            .catch(() => caches.match(request))
    );
});

If the login page is cached, the user will see an outdated version or get "stuck" in a cached state after logging out. The trade-off of this approach: offline mode for HTML pages does not work. If you need it, add a separate offline placeholder page.

Read more about common PWA caching errors — including aggressive HTML caching and routing issues — in the article 8 Critical Mistakes When Integrating PWA: Scenarios, Causes, and Solutions with Code .

💼 Section 6. Push Handler: try/catch Fallback and iOS-Specifics from a Real Project

Every push event in the Service Worker must end with showing a notification, and the payload must be parsed safely: if the data is not in JSON format, the code without try/catch will crash, the notification will not be shown, and iOS may revoke the subscription.

iOS does not support "silent" pushes: if a Service Worker receives a push event and does not display a notification, Safari may revoke the subscription. According to developers, this happens after just a few such instances. Therefore, event.waitUntil with showNotification and a correct fallback for the payload are critically important. [Apple Developer: Sending Web Push Notifications]

If the Service Worker does not display a notification after a push event — iOS considers it a "silent push" and may revoke the subscription. [dev.to: iOS push subscriptions terminated after 3 notifications]

Full push handler from Kazki AI with explanations:


self.addEventListener('push', event => {
    // try/catch — payload might not be JSON (server error, test push)
    let data = {};
    try {
        data = event.data?.json() ?? {};
    } catch (e) {
        data = { title: 'Kazki AI', body: event.data?.text() ?? '' };
    }

    // Badging API — counter on the icon. Badge errors should not
    // break notification display, so we suppress them with catch.
    const badgePromise = self.navigator?.setAppBadge
        ? self.navigator.setAppBadge(data.badgeCount || 1).catch(() => {})
        : Promise.resolve();

    // event.waitUntil + showNotification — MANDATORY for every push event,
    // otherwise iOS may revoke the subscription
    event.waitUntil(
        Promise.all([
            self.registration.showNotification(data.title || 'Kazki AI', {
                body:  data.body || '',
                icon:  data.icon || '/images/favicon.jpg',
                badge: '/images/favicon.jpg',
                data:  { url: data.url || '/dashboard' }
            }),
            badgePromise
        ])
    );
});

// Click on notification — open the relevant page
self.addEventListener('notificationclick', event => {
    event.notification.close();

    const targetUrl = new URL(
        event.notification.data?.url || '/dashboard',
        self.location.origin
    ).href;

    event.waitUntil((async () => {
        if (self.navigator?.clearAppBadge) {
            await self.navigator.clearAppBadge().catch(() => {});
        }

        // If the app is already open — focus on it, don't open a new window
        const windowClients = await clients.matchAll({ type: 'window', includeUncontrolled: true });
        for (const client of windowClients) {
            if ('focus' in client) {
                await client.focus();
                if ('navigate' in client) await client.navigate(targetUrl);
                return;
            }
        }
        await clients.openWindow(targetUrl);
    })());
});

The structure of the payload sent by the backend (Spring Boot + nl.martijndwars/web-push):


{
  "title": "A new fairy tale is ready!",
  "body": "Your fairy tale about the dragon is waiting",
  "icon": "/images/favicon.jpg",
  "url": "/library",
  "badgeCount": 1
}

The badgeCount field should contain the actual number of unread notifications, counted by the server: the Service Worker does not know the previous value.

💼 Section 7. 7 Reasons Why Pushes Don't Arrive on iPhone — and How to Fix Them

Most iOS PWA Push issues are not server-related, but rather how client-side code interacts with Safari's limitations.

Push subscription issues on iOS are one of the most discussed topics on Apple Developer Forums. Below are seven common reasons with specific solutions. [Apple Developer Forums: PWA Push Issues]

1. PWA Not Installed on Home Screen

The most common reason. The Push API is unavailable until the app is opened as a Home Screen web app (standalone mode). Solution: add a UI hint with instructions "Share → Add to Home Screen".

2. Service Worker Doesn't Show Notifications After Push Event

iOS treats pushes without showNotification as "silent" and may revoke the subscription after several such instances. Solution: always call showNotification inside event.waitUntil, even if the payload is empty or invalid. [dev.to: iOS push terminated after 3 notifications]

3. VAPID Subject in Incorrect Format

The VAPID subject must be a mailto: address or an HTTPS URL. If it's incorrect, Apple may reject the request (e.g., with a 403 Forbidden status from web.push.apple.com). Solution: check vapid.subject in the server configuration. [Longsight: Implementing Cross-Platform Push]

4. Subscription "Lost" After Re-authentication

If the frontend calls subscription.unsubscribe() during logout, re-subscribing silently will no longer work: on iOS, a new subscribe() requires a new user gesture, meaning the user will have to click the button again. Based on developer experience, it's most reliable not to unsubscribe on logout, but only to mark it as inactive on the server and link it to the account after the next login. [XenForo: Lost push subscriptions iOS PWA]

5. HTTP Instead of HTTPS

Service Worker and Push API require a secure context, i.e., HTTPS (the only exception is localhost during development; on iPhone, real HTTPS domain is needed for testing). A separate pitfall: the Service Worker file cannot be served via redirect — registration will fail in such a case. Solution: ensure the entire site, including static assets and the SW file, is served via HTTPS without redirects to the script itself.

6. Permission Request Triggered Not by User Gesture

If Notification.requestPermission() is called in setTimeout, DOMContentLoaded, or any automatic code — the request will not work on iOS. Solution: call exclusively within a button click handler. [pwa.io: Web Push iOS Safari 16.4]

7. Endpoint Returned 410 or 404 — Subscription Not Deleted from DB

When a subscription becomes invalid, the push service returns a status of 410 Gone or 404. If this endpoint is not deleted from the database, subsequent attempts to send pushes to this user will waste resources. Solution: handle precisely these status codes and delete the subscription automatically. Other errors (400, 401, 403, 413) usually indicate a problem with your request — VAPID, payload, or its size — so subscriptions should not be deleted based on them: you might lose active subscribers.


// Spring Boot: handling invalid subscriptions
if (statusCode == 410 || statusCode == 404) {
    pushSubscriptionRepository.deleteByEndpoint(sub.getEndpoint());
    log.info("Deleted invalid subscription: {}", sub.getEndpoint());
} else if (statusCode >= 400) {
    // Do not delete: likely an issue with VAPID, payload, or configuration
    log.warn("Push not delivered, status {} for {}", statusCode, sub.getEndpoint());
}

🧩 Ready-made services instead of your own backend: when it makes sense

Everything described above, I implemented myself: VAPID keys, saving subscriptions, and sending via a library on Spring Boot. Many of the problems from the list of seven reasons (invalid subscriptions, VAPID, status handling) lie precisely on the backend, so the natural question arises: why not pass this part to a ready-made service? Below is a brief overview of five popular options. I haven't connected them in production myself, so the comparison is based on their official documentation, not on my own experience. A detailed analysis will be in a separate article: Push services for PWA: OneSignal, FCM, Pushwoosh, WonderPush, and Braze.

What the service doesn't change

The main thing: a ready-made service doesn't remove iOS limitations. The documentation of all checked services describes the same conditions as in this article:

  • For FCM on iOS and iPadOS, the app needs to be added to the home screen.
  • Braze notes that requesting push permission in Safari on iOS is only possible after adding the site to the home screen and only in response to a user action.
  • OneSignal requires iOS 16.4 or newer, adding to the home screen and opening the app from there, and also directly warns that for some iOS developers, web push works initially and then suddenly stops.

This means the service removes some of the backend work, but adding to the home screen, requesting permission with a click, and subscription instability on iOS remain.

Service comparison

Service Type iOS web push (according to documentation) What to consider
Firebase Cloud Messaging (FCM) Delivery infrastructure from Google Yes. Safari on iOS and iPadOS 16.4+, the app must be on the home screen (Firebase Blog) This is closer to your own backend: the service delivers messages, and you build the campaign and segmentation logic yourself. Check the console capabilities in the Firebase documentation.
OneSignal Push notification platform (SaaS) Yes. iOS 16.4+, any browser with "Add to Home Screen" (Safari, Chrome, Edge). OneSignal's Service Worker is mandatory (documentation) You'll have to combine your own Service Worker (caching, push handler) with the service's worker. There's an API for creating messages.
Pushwoosh Push notification platform (SaaS) [Pushwoosh documentation] []
WonderPush Push notification platform (SaaS) Yes. Compatibility with iOS 16.4 was announced on the release day, March 24, 2023 (WonderPush blog) Requires a manifest with "display": "standalone": if you don't have one, the service adds its own. According to the documentation, iOS notifications are text-based, without images or buttons (the page hasn't been updated in a long time, check).
Braze Enterprise-level customer engagement platform (CEP) Yes. Requires a manifest, Service Worker with the Braze library, and adding to the home screen (documentation) A solution for large teams and multi-channel campaigns. Check terms of use and pricing on the official website. If the user has denied the permission request, it can only be shown again after deleting and re-adding the app to the home screen.

Prices are deliberately omitted from the table: they change, so check them on the official pages of each service.

When to use a service, and when to stick with your own backend

A service makes sense if:

  • you don't have the time or resources to maintain your own sending and subscription cleanup;
  • you need segmentation, analytics, and campaigns that the marketing team manages without developers;
  • notifications need to be sent simultaneously from the web and native apps.

Your own backend remains a sensible choice if:

  • the scenario is simple, like Kazki AI ("new fairy tale is ready");
  • it's important that subscriber data doesn't leave your infrastructure;
  • you want minimal dependencies and full control over sending logic.

What to check before choosing

  • iOS audience share. Look at your own analytics to see how many users actually open the site from an iPhone, as all the limitations of this article apply to them.
  • Service Worker conflict. The service's SDK requires its own worker (OneSignal) or importing its library (Braze). If you already have your own caching and push handler, they need to be carefully combined.
  • Vendor lock-in. Subscriptions are stored in the service, so switching to another provider or to your own backend means a portion of users will have to re-subscribe. This is my assumption based on the specifics of Web Push (subscription is tied to keys and provider), check it for your own case.
  • Data and privacy. Check where subscriber data is stored and how it aligns with your privacy policy.
  • New iOS features. If you plan to use Declarative Web Push (iOS 18.4+), check the documentation.
  • 💼 Section 8. Workarounds and fallback strategies

    If PWA Push on iOS is not suitable for your case — email and in-app notifications remain a more reliable alternative, as they do not depend on PWA installation or push subscription status.

    PWA Push on iOS works, but has limitations: it requires installation on the home screen, possible loss of subscriptions, and delivery stability, according to developer experience, is lower than on Android and native apps. For critical notifications, you should have a fallback strategy.

    A reliable notification system is not a choice between Push and email, but a combination of channels with intelligent degradation.

    Email as the primary fallback

    Email does not depend on the iOS version or PWA installation status, and delivery can be tracked (bounce, opens, complaints). For transactional notifications (payments, confirmations, important events), it is a more reliable channel. Limitations: it only works for users whose email address you have, and delivery depends on your domain's reputation (SPF, DKIM, DMARC).

    In-app notifications

    An unread count in the header, an event feed within the app — this is what the user will definitely see the next time they open the PWA, regardless of their push subscription status.

    Combined approach with Kazki AI

    Kazki AI uses a multi-level system:

    1. Push notifications — for users who have installed the PWA and allowed notifications.
    2. Badge on the icon (Badging API) — for installed PWAs, as a supplement to push.
    3. In-app event feed — for everyone who opens the app.
    4. Email — for all users as a fallback and the primary channel for transactional messages.

    This combination significantly expands reach: a user who hasn't installed the PWA or allowed push will still receive important information via email and in-app. No single channel can guarantee 100% reach, but the more independent channels there are, the less likely it is that a notification will be missed.

    More on choosing between PWA and a native app — PWA vs Native in 2026: comparison and a real-world case .

    ❓ Frequently Asked Questions (FAQ)

    Does Web Push work on iOS without installing the PWA?

    No. The Push API on iOS is only available for Home Screen web apps — that is, sites added to the home screen via the Share menu ("Share → Add to Home Screen"). An open tab in Safari or any other browser does not allow subscribing to push. [WebKit Blog: Web Push for Web Apps on iOS]

    Does PWA Push work through Chrome on iPhone?

    Yes, but with a clarification. It's not possible to subscribe to push from a Chrome tab, just as from a Safari tab. However, starting with iOS 16.4, third-party browsers can offer "Add to Home Screen" in the Share menu, and a site added this way opens as a Home Screen web app and has the same push support. The presence of this option depends on the specific browser, so in user instructions, it's more reliable to specify the path "Share → Add to Home Screen". It's also worth knowing that on iOS, browsers use WebKit by default; in the EU, starting with iOS 17.4, Apple also allows alternative engines. [WebKit Blog: Web Push for Web Apps on iOS]

    From which iOS version is Web Push for PWA supported?

    From iOS 16.4 (March 2023). In versions prior to 16.4, Web Push for PWA is unavailable regardless of the browser or settings. Since most active iPhones have long been updated above 16.4, the iOS version is rarely a cause of problems today; you can see the exact share in your audience in your own analytics. [WebKit Blog: Web Push iOS 16.4]

    Why does the subscription disappear after a few days?

    There are several reasons reported by developers. The first is that iOS may cancel the subscription if the Service Worker received push events but did not display notifications. The second is that after prolonged inactivity of the app, the subscription may become invalid (the server will receive 410 or 404). Solution: always call showNotification in event.waitUntil, check the subscription's validity every time the app is opened, and resend it to the server. [dev.to: iOS push subscriptions terminated]

    What is the delivery rate for PWA Push on iOS compared to Android?

    There is no universal figure: there is little independent public data, and the result depends on your audience and the proportion of "dead" subscriptions. According to developer experience, delivery on iOS is consistently worse than on Android: stricter Safari rules regarding notifications and subscriptions becoming invalid after inactivity. The most reliable way is to measure your own metric: compare the number of successful sends (2xx responses) with the total number of subscriptions and separately count how many subscriptions are deleted due to 410/404. [OneSignal: iOS Web Push]

    Can I send a push without a library on the backend?

    Technically yes — the Web Push Protocol is an open standard. But self-implementation of VAPID signing and payload encryption is complex and error-prone. For Java/Spring Boot, you can use the nl.martijndwars:web-push library (it also requires a BouncyCastle provider: before sending, register it via Security.addProvider(new BouncyCastleProvider())), for Node.js — web-push.

    
    <!-- Maven: dependency for Java/Spring Boot.
         Check Maven Central for the current version -->
    <dependency>
        <groupId>nl.martijndwars</groupId>
        <artifactId>web-push</artifactId>
        <version>5.1.1</version>
    </dependency>
    

    How to generate VAPID keys?

    The easiest way is through the web-push CLI:

    
    npx web-push generate-vapid-keys
    

    The public key is used on the frontend during subscription, the private key is used on the backend to sign each push request. Never publish the private key in an open repository. Generate the pair once and save it: existing subscriptions are tied to the public key, so after changing it, users will have to subscribe again.

    ✅ Conclusions: when PWA Push on iOS is sufficient, and when native is better

    PWA Push on iOS works and is suitable for most content and SaaS products. For critical notifications in fintech, medicine, or e-commerce with a large iOS audience share, it's advisable to either combine it with email or consider native.

    Since iOS 16.4, the technical barrier has been removed. The operational one remains: the user must add the PWA to the home screen and allow notifications — and this is the main limitation that cannot be solved at the code level.

    PWA Push on iOS is not "either it works or it doesn't." It's a tool with specific operating conditions. If these conditions are met — it works reliably enough for informational notifications.

    PWA Push on iOS is suitable if:

    • Your product is content-based: news, media, education, SaaS
    • The audience is loyal and willing to add the PWA to their home screen
    • Notifications are informational, not critical (reminders, news, updates)
    • Email is available as a fallback for those who haven't installed the PWA
    • The budget doesn't allow for parallel development of a native app

    Consider native or hybrid if:

    • Notifications are critical: banking transactions, medical alerts
    • The iOS share in your audience is high (a benchmark is over half of users)
    • Real-time background processes or geolocation are needed
    • App Store presence is part of the marketing strategy

    Conclusion from Kazki AI experience

    At Kazki AI, PWA Push on iOS works satisfactorily for informational notifications — "new fairy tale ready," "subscription reminder." Delivery on iOS is less stable than on Android, but acceptable for a product where delivery criticality is not high. Email remains the primary channel for transactional notifications.

    If you are still choosing between a PWA and a native app and haven't decided on the architecture — a detailed comparison with real figures in the article PWA vs Native in 2026: comparison and a real-world case .

    📖 Sources