Notificaciones push para PWA en iOS — uno de los temas más debatidos
entre los desarrolladores web en los últimos dos años. Apple mantuvo esta
función cerrada durante mucho tiempo, y cuando la abrió, lo hizo con limitaciones
que aún generan preguntas en la práctica.
En este artículo — un análisis técnico basado en el proyecto real
Kazki AI:
qué funciona, qué no, y qué código se necesita para que todo funcione en iPhone.
Si aún estás eligiendo entre PWA y una aplicación nativa — primero lee
PWA vs Nativa en 2026: comparación y caso real
.
📚 Contenido del artículo
🎯 Sección 1. Por qué iOS bloqueó las notificaciones Push para PWA durante años
Respuesta corta: hasta marzo de 2023, la API Push para PWA en iOS
estaba desactivada deliberadamente — independientemente de las capacidades técnicas de la plataforma.
Android soportaba Web Push desde 2015. Apple añadió soporte
solo en iOS 16.4 (marzo de 2023) — ocho años después. La razón no es técnica,
sino de negocio: las PWA con notificaciones push completas reducen el incentivo para publicar
aplicaciones en la App Store, donde Apple cobra una comisión del 15-30%.
Antes de iOS 16.4, las aplicaciones web en iPhone físicamente no podían recibir
notificaciones push — no debido a limitaciones de WebKit, sino a una decisión consciente
de Apple de no implementar esta función.
Oficialmente, Apple explicaba la falta de soporte por razones
de privacidad y UX. Técnicamente, la implementación realmente requería
la integración con APNs (Apple Push Notification service) en el contexto del navegador
— pero esto no era un obstáculo insuperable, ya que macOS Safari
soportaba Web Push desde OS X Mavericks (2013).
[Wikipedia: Apple Push Notification service]
La presión real vino de fuera. Los requisitos regulatorios de la UE en el marco
de la Ley de Mercados Digitales obligaron a Apple a revisar su enfoque hacia las aplicaciones web.
En febrero de 2023, Apple incluso planeó limitar la funcionalidad PWA
en la UE al nivel de "marcador web" — pero después de la presión pública de los desarrolladores
retrocedió y, en su lugar, añadió Web Push para todas las regiones simultáneamente.
[Brainhub: PWA on iOS — Current Status]
🎯 Sección 2. iOS 16.4, 17, 18: cronología de cambios — qué apareció y qué sigue roto
Cada versión de iOS mejoró el soporte de PWA Push,
pero el problema de la "desaparición" aleatoria de la suscripción aún no está completamente resuelto.
iOS 16.4 abrió Web Push para las PWA instaladas. iOS 17 habilitó
las API correspondientes por defecto en todos los navegadores. iOS 18 no añadió
cambios drásticos, pero los desarrolladores aún registran casos en los que
la suscripción push "desaparece" sin una razón aparente.
iOS 16.4 — es el comienzo, no el final. Cada actualización posterior
corrigió parcialmente el comportamiento, pero la estabilidad de PWA Push
en iOS todavía es inferior a la de Android.
iOS 16.4 (marzo de 2023) — apertura
Primer lanzamiento con soporte para Web Push para aplicaciones web de la pantalla de inicio.
Las notificaciones push aparecían en la pantalla de bloqueo, en el Centro de Notificaciones
y en el Apple Watch. Se añadió soporte para la API de Badging.
[WebKit: Web Push for Web Apps on iOS and iPadOS]
Pero inmediatamente surgió un problema: las suscripciones push dejaban de funcionar
después de 1-2 semanas. El endpoint se volvía inactivo, y el usuario
tenía que reinstalar la PWA para volver a suscribirse.
[Apple Developer Forums: PWA Push Issues iOS]
iOS 17 (septiembre de 2023) — expansión
La API Web Push se hizo disponible por defecto en todos los navegadores de iOS —
no solo en Safari PWA. La estabilidad mejoró, pero el problema
de la desaparición de las suscripciones persistió para muchos desarrolladores.
[OneSignal: iOS Web Push Setup]
iOS 18 (septiembre de 2024) — estabilización
No hubo cambios significativos en la API Web Push. Algunos desarrolladores registran
mejoras en la fiabilidad de la entrega, otros — los mismos problemas con la pérdida
de suscripciones después de un largo período de inactividad de la aplicación.
[Apple Developer Forums: discusión iOS 17–18]
🎯 Sección 3. Condición principal: solo Safari + Añadir a pantalla de inicio — Chrome no cuenta
Las notificaciones push en iOS funcionan exclusivamente
para PWA, instaladas a través de Safari → Compartir → Añadir a pantalla de inicio.
Una pestaña abierta en cualquier navegador — no cuenta.
A diferencia de Android, donde Chrome puede solicitar permiso para notificaciones push
directamente en el navegador, iOS requiere que la PWA esté instalada como
una aplicación separada en la pantalla de inicio. Chrome en iOS — es WebKit
bajo el capó, y no puede reemplazar a Safari en este proceso.
Web Push no es compatible dentro de Safari en iOS — funciona
solo para aplicaciones web de la pantalla de inicio.
[Apple Developer Forums]
Esto significa que antes de solicitar permiso para las notificaciones,
es necesario verificar dos cosas: si es iOS, y si la aplicación está abierta
en modo independiente (es decir, instalada en la pantalla de inicio).
const isIOS = /iphone|ipad/i.test(navigator.userAgent);
const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
if (isIOS && !isStandalone) {
// Mostrar sugerencia: "Añade a la pantalla de inicio para recibir notificaciones"
showInstallPrompt();
return;
}
Si se omite esta verificación — Notification.requestPermission()
en iOS simplemente devolverá 'default' o lanzará un error,
y el usuario no entenderá por qué el botón "suscribirse" no funciona.
🎯 Sección 4. Paso a paso: suscripción del usuario a Push en iOS
La suscripción en iOS requiere un gesto del usuario —
la solicitud de permiso no se puede invocar automáticamente al cargar la página,
solo en respuesta a una acción del usuario.
A diferencia de Android, donde se puede solicitar permiso programáticamente a través de
setTimeout, iOS Safari exige estrictamente que
Notification.requestPermission() se invoque
directamente desde un manejador de clic. De lo contrario, la solicitud será silenciosamente
ignorada o bloqueada.
[pwa.io: Web Push with iOS Safari 16.4]
"Recuerda, no puedes solicitar una suscripción push sin
un gesto explícito del usuario."
[Apple WWDC22: Meet Web Push for Safari]
Aquí está el flujo completo de suscripción del proyecto real Kazki AI:
const VAPID_PUBLIC_KEY = 'YOUR_PUBLIC_VAPID_KEY';
async function initPushSubscription() {
// 1. Verificamos el soporte
if (!('serviceWorker' in navigator) || !('PushManager' in window)) return;
// 2. Verificación para iOS: solo modo standalone
const isIOS = /iphone|ipad/i.test(navigator.userAgent);
const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
if (isIOS && !isStandalone) {
showInstallPrompt(); // mostramos la sugerencia "Añadir a la pantalla"
return;
}
try {
const reg = await navigator.serviceWorker.ready;
// 3. Verificamos si ya está suscrito
const existing = await reg.pushManager.getSubscription();
if (existing) return;
// 4. Solicitud de permiso — ¡solo después de un gesto del usuario!
const permission = await Notification.requestPermission();
if (permission !== 'granted') return;
// 5. Creamos la suscripción
const subscription = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
});
// 6. Guardamos en el servidor
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('Error de Push:', e.message);
}
}
// Suscribimos solo al hacer clic — ¡no automáticamente!
document.querySelector('#push-btn')
.addEventListener('click', initPushSubscription);
El endpoint de suscripción en iOS siempre comienza con
https://web.push.apple.com/, en Android — con
https://fcm.googleapis.com/. Esto es útil para el diagnóstico.
[pqvst.com: Demystifying Web Push Notifications]
🎯 Sección 5. Web App Manifest y Service Worker: configuraciones obligatorias
sin display: standalone
en manifest.json la API Push en iOS simplemente no aparecerá en el Service Worker,
incluso si todo lo demás está configurado correctamente.
Apple requiere que la PWA sea declarada como "aplicación web de la pantalla de inicio" a través del
manifiesto. Sin este campo registration.pushManager
devuelve undefined en iOS.
[GitHub: webpush-ios-example]
PushManager aparece en el serviceWorker solo después de añadir el sitio
a la pantalla de inicio a través de Safari — y solo si el manifiesto contiene
display: standalone.
manifest.json mínimo para que Push funcione en 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"
}
]
}
Es de vital importancia: los iconos deben ser archivos PNG reales del tamaño correcto.
Un icono ausente o inaccesible puede bloquear la instalación
de la PWA en iOS.
Service Worker: por qué no se puede cachear la autenticación y la API
Un error común — el almacenamiento en caché agresivo que rompe la autenticación.
Aquí está el enfoque de Kazki AI: solo almacenamos en caché el contenido estático, todo lo dinámico
— lo omitimos por completo:
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// No cacheamos: API, autenticación, páginas dinámicas
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;
// Para el resto — network-first, fallback a caché
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))
);
});
Si se almacena en caché la página de autenticación — el usuario verá una versión obsoleta
o "se quedará atascado" en un estado en caché después de cerrar la sesión.
Para más detalles sobre los errores comunes de almacenamiento en caché en PWA — incluyendo el almacenamiento en caché agresivo
de HTML y problemas de enrutamiento — lee el artículo
8 errores críticos al integrar PWA
.
💼 Sección 6. Manejador de Push: fallback con try/catch y especificidades de iOS de un proyecto real
iOS a veces envía un evento push con datos
que no están en formato JSON — sin try/catch el Service Worker fallará y la suscripción
se cancelará automáticamente después de varios de estos fallos.
Apple documenta que si el Service Worker no muestra una notificación
después de recibir un evento push — iOS revoca el permiso de notificación
para ese sitio. Por lo tanto, event.waitUntil y un fallback correcto
son de vital importancia.
[Apple Developer: Sending Web Push Notifications]
Si el Service Worker no muestra una notificación después de un evento push —
iOS lo considera una "notificación silenciosa" y cancela la suscripción.
[dev.to: iOS push subscriptions terminated after 3 notifications]
Manejador push completo de Kazki AI con explicaciones:
self.addEventListener('push', event => {
// try/catch — iOS a veces envía un payload no JSON
let data = {};
try {
data = event.data?.json() ?? {};
} catch (e) {
data = { title: 'Kazki AI', body: event.data?.text() ?? '' };
}
// Badging API — mostramos el contador en el icono
const badgePromise = self.navigator?.setAppBadge
? self.navigator.setAppBadge(data.badgeCount || 1)
: Promise.resolve();
// event.waitUntil — OBLIGATORIO, de lo contrario iOS cancelará la suscripción
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
])
);
});
// Clic en la notificación — abrimos la página deseada
self.addEventListener('notificationclick', event => {
event.notification.close();
if (self.navigator?.clearAppBadge) {
self.navigator.clearAppBadge();
}
// Abrimos la URL del payload o el /dashboard por defecto
event.waitUntil(
clients.openWindow(event.notification.data.url)
);
});
Estructura del payload enviado por el backend (Spring Boot + nl.martijndwars/web-push):
{
"title": "¡Nuevo cuento listo!",
"body": "Tu cuento sobre el dragón ya te espera",
"icon": "/images/favicon.jpg",
"url": "/library",
"badgeCount": 1
}
💼 Sección 7. 7 razones por las que las notificaciones push no llegan a iPhone — y cómo solucionarlo
La mayoría de los problemas con iOS PWA Push
no están relacionados con el servidor, sino con la forma en que el código del cliente interactúa
con las limitaciones de Safari.
Los problemas con las suscripciones push en iOS — uno de los temas más debatidos
en los Foros de Desarrolladores de Apple. A continuación — siete de las razones más comunes
con soluciones específicas.
[Apple Developer Forums: PWA Push Issues]
1. PWA no instalada en la pantalla de inicio
La razón más común. La API Push simplemente no está disponible hasta que la aplicación
no se abre en modo independiente. Solución: añadir una sugerencia de UI
con la instrucción "Compartir → Añadir a la pantalla de inicio".
2. El Service Worker no muestra la notificación después de un evento push
iOS considera una notificación push sin showNotification como "silenciosa"
y cancela la suscripción después de varios de estos casos. Solución:
siempre invocar showNotification dentro de
event.waitUntil.
[dev.to: iOS push terminated after 3 notifications]
3. VAPID subject en formato incorrecto
Apple requiere que el VAPID subject sea una dirección mailto:,
o una URL HTTPS completa. Cualquier otro formato devuelve
403 Forbidden de web.push.apple.com.
Solución: verificar vapid.subject en la configuración del servidor.
[Longsight: Implementing Cross-Platform Push]
4. La suscripción "desapareció" después de la reautenticación
Si el código en el frontend invoca subscription.unsubscribe()
durante el cierre de sesión o la reautenticación — Safari no permitirá
suscribirse de nuevo sin un gesto explícito del usuario. Solución: no
cancelar la suscripción al cerrar sesión, solo desactivarla en el servidor.
[XenForo: Lost push subscriptions iOS PWA]
5. HTTP en lugar de HTTPS
Service Worker y Push API requieren HTTPS sin excepciones.
Incluso una redirección de HTTP a HTTPS puede interrumpir el registro del SW.
Solución: verificar que todo el sitio, incluida la estática,
se sirva a través de HTTPS.
6. La solicitud de permiso no se invoca con un gesto del usuario
Notification.requestPermission() invocado en
setTimeout, DOMContentLoaded o
cualquier código automático — en iOS será bloqueado silenciosamente.
Solución: invocar exclusivamente en el manejador de clic de un botón.
[pwa.io: Web Push iOS Safari 16.4]
7. El Endpoint devolvió 410 o 404 — la suscripción no se eliminó de la BD
Cuando Apple invalida una suscripción — el servidor recibe el estado
410 Gone o 404. Si no se elimina
este endpoint de la base de datos — los intentos posteriores de enviar una notificación push a este
usuario desperdiciarán recursos. Solución:
procesar estos códigos de estado y eliminar la suscripción automáticamente.
// Spring Boot: manejo de suscripciones inválidas
if (statusCode == 410 || statusCode == 404 || statusCode == 400) {
pushSubscriptionRepository.deleteByEndpoint(sub.getEndpoint());
log.info("Suscripción inválida eliminada: {}", sub.getEndpoint());
}
💼 Sección 8. Soluciones alternativas y estrategias de fallback
Si PWA Push en iOS no es adecuado para
su caso — el correo electrónico y las notificaciones en la aplicación siguen siendo una alternativa más fiable
con una tasa de entrega más alta.
PWA Push en iOS funciona, pero tiene limitaciones: requiere instalación,
posibles pérdidas de suscripciones, una tasa de entrega más baja en comparación con las nativas.
Para notificaciones críticas, es aconsejable tener una estrategia de fallback.
Un sistema de notificaciones fiable — no es una elección entre Push y correo electrónico,
sino una combinación de ambos con una degradación inteligente.
Correo electrónico como fallback principal
El correo electrónico tiene una tasa de entrega de aproximadamente 90–95% y no depende de
la versión de iOS o del estado de instalación de la PWA. Para notificaciones transaccionales
(pago, confirmación, eventos importantes), el correo electrónico
sigue siendo un canal más fiable.
Notificaciones en la aplicación
El contador de no leídos en el encabezado, el feed de eventos dentro
de la aplicación — esto es lo que el usuario verá garantizado la próxima vez que
abra la PWA, independientemente del estado de la suscripción push.
Enfoque combinado con Kazki AI
En Kazki AI se utiliza un sistema de tres niveles:
- Notificaciones Push — para PWA instaladas en iOS y Android
- Correo electrónico — para todos los usuarios como fallback
- Insignia en el icono (API de Badging) — para PWA instaladas
Esto permite cubrir el 100% de la audiencia, independientemente de si
el usuario ha instalado la PWA en la pantalla de inicio.
Para más detalles sobre la elección entre PWA y una aplicación nativa —
PWA vs Nativa en 2026: comparación y caso real
.
❓ Preguntas frecuentes (FAQ)
¿Funciona Web Push en iOS sin instalar la PWA?
No. La API Push en iOS está disponible exclusivamente para aplicaciones web de la pantalla de inicio —
es decir, sitios añadidos a través de Safari → Compartir → A la pantalla de inicio.
Una pestaña abierta en Safari o en cualquier otro navegador no tiene
acceso a PushManager.
[WebKit Blog: Web Push for Web Apps on iOS]
¿Funciona PWA Push a través de Chrome en iPhone?
No. Chrome, Firefox y cualquier otro navegador en iOS utilizan
WebKit como motor — es un requisito de Apple. Por lo tanto, todos los navegadores en iOS tienen
las mismas limitaciones con respecto a la API Push. Push funciona solo a través de Safari
siempre que la PWA esté instalada en la pantalla de inicio.
[Apple Developer Forums: Push in Chrome iOS]
¿Desde qué versión de iOS se soporta Web Push para PWA?
Desde iOS 16.4 (marzo de 2023). Las versiones anteriores a 16.4 no soportan
Web Push para PWA independientemente del navegador o la configuración.
Según datos de StatCounter, a principios de 2026, más del
95% de los iPhone en el mundo funcionan con iOS 16 o versiones posteriores.
[WebKit Blog: Web Push iOS 16.4]
¿Por qué la suscripción desaparece después de unos días?
Hay varias razones. La primera — iOS cancela la suscripción si el Service Worker
recibió eventos push pero no mostró la notificación. La segunda — si
la PWA no se abrió durante mucho tiempo, Safari puede borrar el estado
del Service Worker. Solución: siempre invocar showNotification
en event.waitUntil y verificar la actualidad de la suscripción
cada vez que se abre la aplicación.
[dev.to: iOS push subscriptions terminated]
¿Cuál es la tasa de entrega de PWA Push en iOS en comparación con Android?
Según la experiencia de equipos que han compartido datos públicamente — la tasa de entrega
en iOS es de aproximadamente 70–85% frente a 90–95% en Android.
La diferencia se debe a las limitaciones adicionales de Safari: gestión más estricta
de los procesos en segundo plano, posible pérdida de suscripción después de
un largo período de inactividad.
[OneSignal: iOS Web Push delivery]
¿Se puede enviar una notificación push sin una biblioteca en el backend?
Técnicamente sí — el Protocolo Web Push es un estándar abierto. Pero
la implementación independiente de la firma VAPID y el cifrado del payload es compleja
y propensa a errores. Para Java/Spring Boot se recomienda
la biblioteca nl.martijndwars:web-push,
para Node.js — web-push.
<!-- Maven: dependencia para Java/Spring Boot -->
<dependency>
<groupId>nl.martijndwars</groupId>
<artifactId>web-push</artifactId>
<version>5.1.1</version>
</dependency>
¿Cómo generar claves VAPID?
La forma más sencilla — a través de web-push CLI:
npx web-push generate-vapid-keys
La clave pública se utiliza en el frontend para la suscripción,
la privada — en el backend para firmar cada solicitud push.
Nunca publiques la clave privada en un repositorio público.
✅ Conclusiones: cuándo PWA Push en iOS es suficiente y cuándo es mejor una nativa
PWA Push en iOS funciona y es adecuado para
la mayoría de los productos de contenido y SaaS. Para notificaciones críticas
en fintech, medicina o e-commerce con una gran proporción de audiencia de iOS
se debe combinar con correo electrónico o considerar una aplicación nativa.
Con iOS 16.4, la barrera técnica ha sido eliminada. Queda la operativa:
el usuario debe instalar la PWA en la pantalla de inicio — y esta es la principal
limitación que no se resuelve a nivel de código.
PWA Push en iOS — no es "o funciona o no". Es una herramienta
con condiciones de funcionamiento específicas. Si estas condiciones se cumplen —
funciona de manera estable.
PWA Push en iOS es adecuado si:
- Su producto es de contenido: noticias, medios, educación, SaaS
- La audiencia es leal y está dispuesta a instalar la PWA en la pantalla de inicio
- Las notificaciones son informativas, no críticas (recordatorios, noticias, actualizaciones)
- Hay correo electrónico como fallback para quienes no instalaron la PWA
- El presupuesto no permite desarrollar una aplicación nativa en paralelo
Se debe considerar una aplicación nativa o híbrida si:
- Las notificaciones son críticas: transacciones bancarias, alertas médicas
- La proporción de iOS en su audiencia supera el 60%
- Se requieren procesos en segundo plano o geolocalización en tiempo real
- La presencia en la App Store es parte de la estrategia de marketing
Conclusión de la experiencia de Kazki AI
En Kazki AI, PWA Push en iOS funciona satisfactoriamente para notificaciones
informativas — "nuevo cuento listo", "recordatorio de suscripción".
La tasa de entrega es más baja que en Android, pero aceptable para un producto
donde la criticidad de la entrega no es alta. El correo electrónico sigue siendo el canal
principal para las notificaciones transaccionales.
Si aún estás eligiendo entre PWA y una aplicación nativa y no has decidido
la arquitectura — una comparación detallada con cifras reales en el artículo
PWA vs Nativa en 2026: comparación y caso real
.
📖 Fuentes