Push-сповіщення для PWA на iOS — одна з найбільш обговорюваних тем
серед веб-розробників останніх двох років. Apple довго тримала цю
функцію закритою, а коли відкрила — зробила це з обмеженнями,
які досі викликають питання на практиці.
У цій статті — технічний розбір на основі реального проєкту
Kazki AI:
що працює, що ні, і який код потрібен щоб все запрацювало на iPhone.
Якщо ви ще вибираєте між PWA та нейтив-додатком — спочатку прочитайте
PWA vs Native у 2026: порівняння та реальний кейс
.
📚 Зміст статті
🎯 Розділ 1. Чому iOS роками блокував Push-сповіщення для PWA
Коротка відповідь: до березня 2023 року Push API для PWA на iOS
був свідомо відключений — незалежно від технічних можливостей платформи.
Android підтримував Web Push з 2015 року. Apple додала підтримку
лише у iOS 16.4 (березень 2023) — через вісім років. Причина не технічна,
а бізнесова: PWA з повноцінними пушами зменшує стимул публікувати
додатки в App Store, де Apple стягує комісію 15–30%.
До iOS 16.4 веб-додатки на iPhone фізично не могли отримувати
push-сповіщення — не через обмеження WebKit, а через свідоме
рішення Apple не впроваджувати цю функцію.
Офіційно Apple пояснювала відсутність підтримки міркуваннями
конфіденційності та UX. Технічно реалізація справді потребувала
інтеграції з APNs (Apple Push Notification service) у браузерному
контексті — але це не було нездоланною перешкодою, адже macOS Safari
підтримував Web Push ще з OS X Mavericks (2013).
[Wikipedia: Apple Push Notification service]
Реальний тиск прийшов іззовні. Регуляторні вимоги ЄС у рамках
Digital Markets Act змусили Apple переглянути підхід до веб-додатків.
У лютому 2023 Apple навіть планувала обмежити PWA-функціональність
в ЄС до рівня "веб-закладки" — але після публічного тиску розробників
відступила і натомість додала Web Push для всіх регіонів одночасно.
[Brainhub: PWA on iOS — Current Status]
🎯 Розділ 2. iOS 16.4, 17, 18: хронологія змін — що з'явилось і що досі зламано
Кожна версія iOS покращувала підтримку PWA Push,
але проблема з випадковим "зникненням" підписки досі повністю не вирішена.
iOS 16.4 відкрив Web Push для встановлених PWA. iOS 17 увімкнув
відповідні API за замовчуванням у всіх браузерах. iOS 18 не додав
кардинальних змін, але розробники все ще фіксують випадки коли
push-підписка "зникає" без очевидної причини.
iOS 16.4 — це старт, а не фінал. Кожне наступне оновлення
частково виправляло поведінку, але стабільність PWA Push
на iOS досі поступається Android.
iOS 16.4 (березень 2023) — відкриття
Перший реліз з підтримкою Web Push для Home Screen web apps.
Push-сповіщення з'являлись на Lock Screen, у Notification Center
та на Apple Watch. Додана підтримка Badging API.
[WebKit: Web Push for Web Apps on iOS and iPadOS]
Але одразу виявилась проблема: push-підписки через 1–2 тижні
переставали працювати. Ендпоінт ставав неактивним, і користувач
мав переустановити PWA щоб підписатись знову.
[Apple Developer Forums: PWA Push Issues iOS]
iOS 17 (вересень 2023) — розширення
Web Push API став доступний за замовчуванням у всіх браузерах на iOS —
не тільки в Safari PWA. Покращена стабільність, але проблема
зникнення підписок залишилась для багатьох розробників.
[OneSignal: iOS Web Push Setup]
iOS 18 (вересень 2024) — стабілізація
Суттєвих змін у Web Push API не було. Частина розробників фіксує
покращення надійності доставки, інші — ті самі проблеми з втратою
підписок після тривалої неактивності додатку.
[Apple Developer Forums: обговорення iOS 17–18]
🎯 Розділ 3. Головна умова: тільки Safari + Add to Home Screen — Chrome не рахується
Push-сповіщення на iOS працюють виключно
для PWA, встановленої через Safari → Share → Add to Home Screen.
Відкрита вкладка в будь-якому браузері — не рахується.
На відміну від Android, де Chrome може запросити дозвіл на пуші
прямо у браузері, iOS вимагає щоб PWA була встановлена як
окремий додаток на домашньому екрані. Chrome на iOS — це WebKit
під капотом, і він не може замінити Safari у цьому процесі.
Web Push is not supported inside Safari on iOS — він працює
тільки для Home Screen web apps.
[Apple Developer Forums]
Це означає що перед тим як запитувати дозвіл на сповіщення,
потрібно перевірити дві речі: чи це iOS, і чи додаток відкритий
у standalone-режимі (тобто встановлений на домашній екран).
const isIOS = /iphone|ipad/i.test(navigator.userAgent);
const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
if (isIOS && !isStandalone) {
// Показати підказку: "Додай на домашній екран щоб отримувати сповіщення"
showInstallPrompt();
return;
}
Якщо пропустити цю перевірку — Notification.requestPermission()
на iOS просто поверне 'default' або викине помилку,
і користувач не зрозуміє чому кнопка "підписатись" не працює.
🎯 Розділ 4. Покроково: підписка користувача на Push на iOS
Підписка на iOS вимагає user gesture —
запит дозволу не можна викликати автоматично при завантаженні сторінки,
лише у відповідь на дію користувача.
На відміну від Android, де можна запросити дозвіл програмно через
setTimeout, iOS Safari суворо вимагає щоб
Notification.requestPermission() викликався
безпосередньо з обробника кліку. Інакше запит буде мовчки
проігнорований або заблокований.
[pwa.io: Web Push with iOS Safari 16.4]
"Remember, you cannot request a push subscription without
an explicit user gesture."
[Apple WWDC22: Meet Web Push for Safari]
Ось повний flow підписки з реального проєкту Kazki AI:
const VAPID_PUBLIC_KEY = 'YOUR_PUBLIC_VAPID_KEY';
async function initPushSubscription() {
// 1. Перевіряємо підтримку
if (!('serviceWorker' in navigator) || !('PushManager' in window)) return;
// 2. Перевірка для iOS: тільки standalone-режим
const isIOS = /iphone|ipad/i.test(navigator.userAgent);
const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
if (isIOS && !isStandalone) {
showInstallPrompt(); // показуємо підказку "Додай на екран"
return;
}
try {
const reg = await navigator.serviceWorker.ready;
// 3. Перевіряємо чи вже підписаний
const existing = await reg.pushManager.getSubscription();
if (existing) return;
// 4. Запит дозволу — тільки після user gesture!
const permission = await Notification.requestPermission();
if (permission !== 'granted') return;
// 5. Створюємо підписку
const subscription = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
});
// 6. Зберігаємо на сервері
await fetch('/api/push/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
endpoint: subscription.endpoint,
p256dh: subscription.toJSON().keys.p256dh,
auth: subscription.toJSON().keys.auth
})
});
} catch (e) {
console.error('Push помилка:', e.message);
}
}
// Підписуємо тільки по кліку — не автоматично!
document.querySelector('#push-btn')
.addEventListener('click', initPushSubscription);
Ендпоінт підписки на iOS завжди починається з
https://web.push.apple.com/, на Android — з
https://fcm.googleapis.com/. Це корисно для діагностики.
[pqvst.com: Demystifying Web Push Notifications]
🎯 Розділ 5. Web App Manifest та Service Worker: обов'язкові налаштування
без display: standalone
у manifest.json Push API на iOS просто не з'явиться у Service Worker,
навіть якщо все інше налаштовано правильно.
Apple вимагає щоб PWA була оголошена як "Home Screen web app" через
маніфест. Без цього поля registration.pushManager
повертає undefined на iOS.
[GitHub: webpush-ios-example]
PushManager з'являється в serviceWorker лише після додавання сайту
на домашній екран через Safari — і лише якщо маніфест містить
display: standalone.
Мінімальний manifest.json для роботи Push на 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"
}
]
}
Критично важливо: іконки мають бути реальними PNG-файлами коректного
розміру. Відсутня або недоступна іконка може заблокувати встановлення
PWA на iOS.
Service Worker: чому не можна кешувати авторизацію та API
Типова помилка — агресивне кешування, яке ламає авторизацію.
Ось підхід з Kazki AI: кешуємо тільки статику, все динамічне
— пропускаємо повністю:
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// Не кешуємо: API, авторизація, динамічні сторінки
if (event.request.method !== 'GET') return;
if (url.pathname.startsWith('/api/')) return;
if (url.pathname.startsWith('/login') ||
url.pathname.startsWith('/logout') ||
url.pathname.startsWith('/oauth2')) return;
if (url.pathname.startsWith('/dashboard') ||
url.pathname.startsWith('/profile')) return;
// Для решти — network-first, fallback на кеш
event.respondWith(
fetch(event.request)
.then(response => {
if (response.ok) {
const clone = response.clone();
caches.open(CACHE_NAME)
.then(cache => cache.put(event.request, clone));
}
return response;
})
.catch(() => caches.match(event.request))
);
});
Якщо закешувати сторінку авторизації — користувач побачить застарілу
версію або "застрягне" у кешованому стані після виходу з акаунту.
Детальніше про типові помилки кешування у PWA — включно з агресивним
кешуванням HTML та проблемами роутингу — читайте у статті
8 критичних помилок при інтеграції PWA
.
💼 Розділ 6. Push handler: try/catch fallback та iOS-специфіка з реального проєкту
iOS іноді надсилає push-подію з даними
не у JSON-форматі — без try/catch Service Worker впаде і підписка
буде автоматично скасована після кількох таких збоїв.
Apple документує що якщо Service Worker не відображає сповіщення
після отримання push-події — iOS відкликає дозвіл на сповіщення
для цього сайту. Тому event.waitUntil і коректний
fallback є критично важливими.
[Apple Developer: Sending Web Push Notifications]
Якщо Service Worker не відображає сповіщення після push-події —
iOS розцінює це як "тихий пуш" і скасовує підписку.
[dev.to: iOS push subscriptions terminated after 3 notifications]
Повний push handler з Kazki AI з поясненнями:
self.addEventListener('push', event => {
// try/catch — iOS іноді шле не-JSON payload
let data = {};
try {
data = event.data?.json() ?? {};
} catch (e) {
data = { title: 'Kazki AI', body: event.data?.text() ?? '' };
}
// Badging API — показуємо лічильник на іконці
const badgePromise = self.navigator?.setAppBadge
? self.navigator.setAppBadge(data.badgeCount || 1)
: Promise.resolve();
// event.waitUntil — ОБОВ'ЯЗКОВО, інакше iOS скасує підписку
event.waitUntil(
Promise.all([
self.registration.showNotification(data.title || 'Kazki AI', {
body: data.body || '',
icon: '/images/favicon.jpg',
badge: '/images/favicon.jpg',
data: { url: data.url || '/dashboard' }
}),
badgePromise
])
);
});
// Клік по сповіщенню — відкриваємо потрібну сторінку
self.addEventListener('notificationclick', event => {
event.notification.close();
if (self.navigator?.clearAppBadge) {
self.navigator.clearAppBadge();
}
// Відкриваємо URL з payload або дефолтний /dashboard
event.waitUntil(
clients.openWindow(event.notification.data.url)
);
});
Структура payload який відправляє бекенд (Spring Boot + nl.martijndwars/web-push):
{
"title": "Нова казка готова!",
"body": "Твоя казка про дракона вже чекає",
"icon": "/images/favicon.jpg",
"url": "/library",
"badgeCount": 1
}
💼 Розділ 7. 7 причин чому пуші не приходять на iPhone — і як це виправити
Більшість проблем з iOS PWA Push
пов'язані не з сервером, а з тим як клієнтський код взаємодіє
з обмеженнями Safari.
Проблеми з push-підписками на iOS — одна з найбільш обговорюваних
тем на Apple Developer Forums. Нижче — сім найпоширеніших причин
з конкретними рішеннями.
[Apple Developer Forums: PWA Push Issues]
1. PWA не встановлена на домашній екран
Найпоширеніша причина. Push API просто недоступний поки додаток
не відкритий у standalone-режимі. Рішення: додати UI-підказку
з інструкцією "Поділитись → Додати на екран".
2. Service Worker не відображає сповіщення після push-події
iOS розцінює пуш без showNotification як "тихий"
і скасовує підписку після кількох таких випадків. Рішення:
завжди викликати showNotification всередині
event.waitUntil.
[dev.to: iOS push terminated after 3 notifications]
3. VAPID subject у неправильному форматі
Apple вимагає щоб VAPID subject був або mailto: адресою,
або повним HTTPS URL. Будь-який інший формат повертає
403 Forbidden від web.push.apple.com.
Рішення: перевірити vapid.subject у конфігурації сервера.
[Longsight: Implementing Cross-Platform Push]
4. Підписка "зникла" після повторної авторизації
Якщо код на фронтенді викликає subscription.unsubscribe()
під час logout або reauthentication — Safari не дозволить
підписатись знову без явного user gesture. Рішення: не
скасовувати підписку при logout, лише деактивувати її на сервері.
[XenForo: Lost push subscriptions iOS PWA]
5. HTTP замість HTTPS
Service Worker і Push API вимагають HTTPS без винятків.
Навіть редирект з HTTP на HTTPS може перервати реєстрацію SW.
Рішення: перевірити що весь сайт, включно зі статикою,
обслуговується через HTTPS.
6. Запит дозволу викликається не з user gesture
Notification.requestPermission() викликаний у
setTimeout, DOMContentLoaded або
будь-якому автоматичному коді — на iOS буде заблокований мовчки.
Рішення: викликати виключно в обробнику кліку на кнопку.
[pwa.io: Web Push iOS Safari 16.4]
7. Endpoint повернув 410 або 404 — підписка не видалена з БД
Коли Apple інвалідує підписку — сервер отримує статус
410 Gone або 404. Якщо не видалити
цей endpoint з бази — наступні спроби надіслати пуш цьому
користувачу будуть марно витрачати ресурси. Рішення:
обробляти ці статус-коди і видаляти підписку автоматично.
// Spring Boot: обробка невалідних підписок
if (statusCode == 410 || statusCode == 404 || statusCode == 400) {
pushSubscriptionRepository.deleteByEndpoint(sub.getEndpoint());
log.info("Видалено невалідну підписку: {}", sub.getEndpoint());
}
💼 Розділ 8. Обхідні шляхи та fallback-стратегії
Якщо PWA Push на iOS не підходить для
вашого кейсу — email і in-app сповіщення залишаються надійнішою
альтернативою з вищим delivery rate.
PWA Push на iOS працює, але має обмеження: потрібна установка,
можливі втрати підписок, нижчий delivery rate порівняно з нейтив.
Для критичних сповіщень варто мати fallback-стратегію.
Надійна система сповіщень — це не вибір між Push і email,
а комбінація обох з розумною деградацією.
Email як основний fallback
Email має delivery rate близько 90–95% і не залежить від
версії iOS або статусу установки PWA. Для транзакційних
сповіщень (оплата, підтвердження, важливі події) email
залишається надійнішим каналом.
In-app сповіщення
Лічильник непрочитаних у заголовку, стрічка подій всередині
додатку — це те що гарантовано побачить користувач при
наступному відкритті PWA, незалежно від статусу push-підписки.
Комбінований підхід з Kazki AI
У Kazki AI використовується трирівнева система:
- Push-сповіщення — для встановлених PWA на iOS і Android
- Email — для всіх користувачів як fallback
- Badge на іконці (Badging API) — для встановлених PWA
Це дозволяє охопити 100% аудиторії незалежно від того,
чи встановив користувач PWA на домашній екран.
Детальніше про вибір між PWA та нейтив-додатком —
PWA vs Native у 2026: порівняння та реальний кейс
.
❓ Часті питання (FAQ)
Чи працює Web Push на iOS без встановлення PWA?
Ні. Push API на iOS доступний виключно для Home Screen web apps —
тобто сайтів, доданих через Safari → Поділитись → На екран "Домівка".
Відкрита вкладка в Safari або будь-якому іншому браузері не має
доступу до PushManager.
[WebKit Blog: Web Push for Web Apps on iOS]
Чи працює PWA Push через Chrome на iPhone?
Ні. Chrome, Firefox та будь-який інший браузер на iOS використовують
WebKit як рушій — це вимога Apple. Тому всі браузери на iOS мають
однакові обмеження щодо Push API. Push працює лише через Safari
при умові що PWA встановлена на домашній екран.
[Apple Developer Forums: Push in Chrome iOS]
З якої версії iOS підтримується Web Push для PWA?
З iOS 16.4 (березень 2023). Версії до 16.4 не підтримують
Web Push для PWA незалежно від браузера або налаштувань.
За даними StatCounter, станом на початок 2026 року більше
95% iPhone у світі працюють на iOS 16 або новіших версіях.
[WebKit Blog: Web Push iOS 16.4]
Чому підписка зникає через кілька днів?
Є кілька причин. Перша — iOS скасовує підписку якщо Service Worker
отримував push-події але не відображав сповіщення. Друга — якщо
PWA не відкривалась тривалий час, Safari може очистити стан
Service Worker. Рішення: завжди викликати showNotification
у event.waitUntil і перевіряти актуальність підписки
при кожному відкритті додатку.
[dev.to: iOS push subscriptions terminated]
Який delivery rate у PWA Push на iOS порівняно з Android?
За досвідом команд що публічно ділились даними — delivery rate
на iOS становить приблизно 70–85% проти 90–95% на Android.
Різниця пов'язана з додатковими обмеженнями Safari: строгіше
управління фоновими процесами, можлива втрата підписки після
тривалої неактивності.
[OneSignal: iOS Web Push delivery]
Чи можна надіслати пуш без бібліотеки на бекенді?
Технічно так — Web Push Protocol є відкритим стандартом. Але
самостійна реалізація VAPID-підпису і шифрування payload складна
і схильна до помилок. Для Java/Spring Boot рекомендується
бібліотека nl.martijndwars:web-push,
для Node.js — web-push.
<!-- Maven: залежність для Java/Spring Boot -->
<dependency>
<groupId>nl.martijndwars</groupId>
<artifactId>web-push</artifactId>
<version>5.1.1</version>
</dependency>
Як згенерувати VAPID ключі?
Найпростіший спосіб — через web-push CLI:
npx web-push generate-vapid-keys
Публічний ключ використовується на фронтенді при підписці,
приватний — на бекенді для підпису кожного push-запиту.
Ніколи не публікуйте приватний ключ у відкритому репозиторії.
✅ Висновки: коли PWA Push на iOS достатньо, а коли краще нейтив
PWA Push на iOS працює і підходить для
більшості контентних та SaaS-продуктів. Для критичних сповіщень
у фінтех, медицині або e-commerce з великою часткою iOS-аудиторії
варто або комбінувати з email, або розглядати нейтив.
З iOS 16.4 технічний бар'єр знятий. Залишається операційний:
користувач має встановити PWA на домашній екран — і це головне
обмеження, яке не вирішується на рівні коду.
PWA Push на iOS — це не "або працює, або ні". Це інструмент
з конкретними умовами роботи. Якщо ці умови виконані —
він працює стабільно.
PWA Push на iOS підходить якщо:
- Ваш продукт — контентний: новини, медіа, освіта, SaaS
- Аудиторія лояльна і готова встановити PWA на домашній екран
- Сповіщення інформаційні, не критичні (нагадування, новини, оновлення)
- Є email як fallback для тих хто не встановив PWA
- Бюджет не дозволяє паралельно розробляти нейтив-додаток
Варто розглянути нейтив або гібрид якщо:
- Сповіщення критичні: банківські транзакції, медичні алерти
- Частка iOS у вашій аудиторії перевищує 60%
- Потрібні фонові процеси або геолокація у реальному часі
- App Store presence є частиною маркетингової стратегії
Висновок з досвіду Kazki AI
У Kazki AI PWA Push на iOS працює задовільно для інформаційних
сповіщень — "нова казка готова", "нагадування про підписку".
Delivery rate нижчий ніж на Android, але прийнятний для продукту
де критичність доставки не висока. Email залишається основним
каналом для транзакційних сповіщень.
Якщо ви ще вибираєте між PWA та нейтив-додатком і не визначились
з архітектурою — детальне порівняння з реальними цифрами у статті
PWA vs Native у 2026: порівняння та реальний кейс
.
📖 Джерела