Probablemente estés en el punto donde la app funciona, los usuarios ya iniciaron sesión y el equipo de producto ahora quiere flujos de re-engagement que se sientan nativos. Recordatorios de carrito. Solicitudes de reseña. Alertas de nuevos mensajes. Anuncios de lanzamientos. El primer instinto suele ser «simplemente configurar push», pero una semana después estás depurando por qué un dispositivo recibe alertas, el simulador parece registrarse bien y nadie puede explicar por qué los taps no abren la pantalla correcta.
Es ahí donde las notificaciones push de Expo son agradablemente simples o sorprendentemente frágiles.
Expo ofrece a los equipos de React Native una capa práctica sobre APNs y FCM, y por eso tantos equipos lo usan. Pero la brecha entre una demo y una implementación lista para producción es real. El ciclo de vida de los tokens, el timing de los permisos, la configuración de listeners, el diseño del payload y la limpieza del backend importan. Si además lanzas cambios frecuentes en la lógica de la app, la necesidad de disciplina operativa se agudiza, especialmente si tu trabajo de retención depende de mensajería fiable y velocidad de release. La entrega solo es útil si la experiencia de usuario alrededor de ella es predecible.
Tabla de contenidos
- La base para involucrar usuarios con notificaciones push de Expo
- Configuración y setup inicial del proyecto
- Solicitar permisos y capturar tokens de push
- Cómo enviar notificaciones desde tu servidor
- Manejo de notificaciones entrantes en tu app
- Mejores prácticas en producción y errores comunes
La base para involucrar a los usuarios con notificaciones push de Expo
Una Notificación push de Expo La configuración es atractiva por una razón sobre todas las demás: elimina mucha de la complejidad del mensajería nativa que los equipos no quieren asumir desde el primer día. En lugar de construir primero la infraestructura directa de APNs y FCM, puedes trabajar con el gateway de Expo y centrarte en el comportamiento del producto, el enrutamiento, la UX de permisos y la lógica de mensajes del backend.
Esa abstracción no hace que los push sean “menos reales”. Solo cambia dónde inviertes tu esfuerzo de ingeniería.
El servicio también es lo suficientemente rápido como para que el rendimiento normalmente no sea lo primero en preocuparte. Del 14 de marzo de 2023 al 12 de junio de 2023, el API de notificaciones push de Expo mostró un tiempo de respuesta mediano de 42 milisegundos, latencia p99 de 273 milisegundos, y un tasa de error diaria media del 0,17 % en decenas de millones de mensajes diarios, according to Knock’s Expo push API benchmark analysis. Eso debería tranquilizar a cualquier equipo que se pregunte si Expo solo sirve para prototipos.
Lo que Expo realmente abstraе
Cuando los equipos dicen “Expo push”, suelen referirse a varias preocupaciones distintas agrupadas:
- Enrutamiento de proveedor: Expo reenvía los mensajes a APNs para iOS y FCM para Android.
- Formato del token: Tu servidor almacena y envía un Expo Push Token en lugar de gestionar primero tokens específicos de cada plataforma.
- Contrato de la solicitud: Envías un payload por POST a la API de push de Expo, en lugar de integrar directamente las APIs de los proveedores nativos.
Eso es útil, pero también genera un malentendido común. A veces los equipos asumen que Expo se encarga de todos los problemas de entrega. En la práctica, muchos fallos se deben a la app code, tokens obsoletos, payloads mal formados o un flujo de permisos deficiente.
Regla práctica: Considera Expo como una capa de transporte confiable, no como un sustituto de un buen diseño de cliente y backend.
Qué significa realmente estar listo para producción
Una demo funcional solo demuestra que un dispositivo aceptó un payload una vez. Estar listo para producción significa otra cosa:
| Aspecto | Mentalidad de demo | Mentalidad de producción |
|---|---|---|
| Permisos | Pregunta ahora | Pregunta en contexto, cuando el valor sea claro |
| Tokens | Guarda una vez | Refresca, deduplica, expira y reconcilia |
| Payloads | Incluye todo data |
Mantén los payloads pequeños y orientados a la acción |
| Comportamiento de la app | Mostrar una alerta | Navegar correctamente y manejar el estado en primer plano |
| Operaciones | Pruebas manuales | Recibos, limpieza, logs y gestión de incidentes |
Esa es la diferencia entre “enviar notificaciones” y “que las notificaciones soporten un flujo de trabajo de producto real”.
Configuración y setup inicial del proyecto
Muchos problemas con las notificaciones push de Expo empiezan antes del primer prompt de permisos. Si la configuración del proyecto es descuidada, el code del cliente puede parecer correcto mientras la app sigue comportándose de forma inconsistente entre builds.

Empieza con las librerías adecuadas instaladas y un entorno de desarrollo que coincida con tu ruta de build. Si trabajas más allá de Expo Go, conviene alinear tu workflow de release con Guía de la alternativa de Capgo a Expo EAS, ya que el comportamiento de las notificaciones suele requerir validación en un build que se asemeje más a producción que una ejecución rápida en sandbox.
Instalar los paquetes de notificaciones
Como mínimo, normalmente necesitarás:
expo-notificationspara solicitudes de permisos, obtención de tokens, listeners y presentación de notificaciones.expo-deviceporque deberías proteger la obtención del token conDevice.isDevice.
Los comandos de instalación típicos dependen de tu gestor de paquetes, pero la clave es alinear las versiones con tu Expo SDK. No mezcles versiones de paquetes arbitrarias; deja que Expo resuelva las compatibles.
Añade configuración a nivel de proyecto
Mantén tu config explícita. Una configuración mínima app.json o app.config.js debe reflejar que las notificaciones son parte del contrato de tu app, no algo secundario.
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"plugins": ["expo-notifications"],
"ios": {
"bundleIdentifier": "com.example.myapp"
},
"android": {
"package": "com.example.myapp"
},
"extra": {
"eas": {
"projectId": "your-project-id"
}
}
}
}
Algunos detalles son clave aquí:
- Bundle identifiers y nombres de paquete deben coincidir con la app que lanzas.
- El plugin de notificaciones asegura que el proyecto nativo tenga el setup necesario durante el build.
- El EAS project ID es importante cuando la obtención del token requiere que la app esté asociada al proyecto de Expo correcto.
Configura pronto un handler de notificaciones
Muchos tutoriales básicos tardan demasiado en definir el comportamiento de las notificaciones. No lo hagas. Defínelo cerca del inicio de la app para que el comportamiento en primer plano sea predecible.
import * as Notifications from 'expo-notifications';
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: false,
shouldSetBadge: true,
}),
});
Los equipos deciden si las notificaciones en primer plano deben mostrar una alerta, reproducir un sonido o afectar a las insignias. El comportamiento exacto depende de tu producto. Una app de chat y una de pagos no tomarán las mismas decisiones.
Si no defines el comportamiento en primer plano de forma intencional, tu equipo terminará depurando notificaciones «faltantes» que en realidad se recibieron, pero nunca se mostraron como esperaba el producto.
Android requiere configuración de canales
Los canales de notificación de Android no son opcionales en la práctica. Si los omites, tus alertas pueden verse inconsistentes o no cumplir las expectativas del usuario.
import { Platform } from 'react-native';
import * as Notifications from 'expo-notifications';
export async function configureAndroidNotifications() {
if (Platform.OS !== 'android') return;
await Notifications.setNotificationChannelAsync('default', {
name: 'Default',
importance: Notifications.AndroidImportance.MAX,
});
}
Configura esto durante la inicialización de la app. Luego, mantén los IDs de canal estables. Cambiarlos sin motivo hace que el comportamiento de las notificaciones sea más difícil de predecir después.
Solicitar permiso y capturar tokens de push
Esta es la parte que muchos equipos copian de un snippet y luego terminan lamentando.
Las solicitudes de permisos requieren timing, conocimiento de la plataforma y un manejo asincrónico disciplinado. La captura del token debe ocurrir solo en un dispositivo físico, solo después de resolver los permisos y solo si estás listo para almacenar el resultado en tu backend de inmediato.

La función del cliente que debería ser tu línea base
Usa una función como esta como punto de partida:
import * as Device from 'expo-device';
import * as Notifications from 'expo-notifications';
import Constants from 'expo-constants';
import { Platform } from 'react-native';
type RegisterResult =
| { ok: true; token: string }
| { ok: false; reason: string };
export async function registerForExpoPushNotificationsAsync(): Promise<RegisterResult> {
if (!Device.isDevice) {
return { ok: false, reason: 'Push notifications require a physical device.' };
}
if (Platform.OS === 'android') {
await Notifications.setNotificationChannelAsync('default', {
name: 'Default',
importance: Notifications.AndroidImportance.MAX,
});
}
const permissions = await Notifications.getPermissionsAsync();
let finalStatus = permissions.status;
if (finalStatus !== 'granted') {
const request = await Notifications.requestPermissionsAsync();
finalStatus = request.status;
}
if (finalStatus !== 'granted') {
return { ok: false, reason: 'Notification permission was not granted.' };
}
const projectId =
Constants.expoConfig?.extra?.eas?.projectId ??
Constants.easConfig?.projectId;
if (!projectId) {
return { ok: false, reason: 'Missing EAS project ID configuration.' };
}
const tokenResponse = await Notifications.getExpoPushTokenAsync({ projectId });
return { ok: true, token: tokenResponse.data };
}
El orden importa. Primero verificas el tipo de dispositivo, configuras el comportamiento del canal de Android, resuelves los permisos, validas la configuración del proyecto y luego solicitas el token de Expo.
Por qué Device.isDevice no es opcional
Este es uno de los pocos errores que genera mucho ruido mientras parece inofensivo. Los equipos expertos solo solicitan el permiso condicionalmente cuando Device.isDevice es true, y una trampa común es omitir esa guarda, lo que lleva a los desarrolladores a enviar notificaciones a tokens de simulador inválidos y culpar a Expo cuando el problema es realmente la configuración de la app, como se describe en Notas de implementación de notificaciones de Expo de Eagerworks.
Por eso la comprobación está al inicio de la función. No la escondas detrás de un helper. Hazla obvia.
Los resultados del simulador son útiles para probar la UI. No son fiables para validar el registro del token de push.
Pide permiso en el momento adecuado
No lo pidas en la pantalla de splash. No lo pidas antes de que el usuario entienda el valor. El mejor momento suele ser después de una acción del usuario que haga concretos los beneficios de las notificaciones, como habilitar actualizaciones de entrega, unirse a una conversación o guardar un elemento observado.
Una buena implementación suele seguir este flujo:
- El usuario llega a un límite funcional relevante.
- La app explica el valor de las notificaciones en tu propia UI.
- La app solicita el permiso del sistema.
- Si se concede el permiso, la app guarda el token en el backend de inmediato.
Ese paso final es donde fallan muchas apps. Obtienen el token, lo registran localmente y posponen el registro en el backend. Luego, soporte no puede saber qué dispositivo tenía qué token en qué momento.
Aquí tienes un ejemplo sencillo de cómo guardar el token tras el registro:
export async function enablePushForCurrentUser(userId: string) {
const result = await registerForExpoPushNotificationsAsync();
if (!result.ok) {
return result;
}
await fetch('https://api.example.com/push-tokens', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer user-session-token',
},
body: JSON.stringify({
userId,
token: result.token,
platform: Platform.OS,
}),
});
return result;
}
Más adelante en el flujo, este recorrido sirve como referencia visual:
Para equipos con apps de lanzamientos frecuentes, también ayuda ver el registro del token como parte del estado operativo de la app, no solo del onboarding. Ese enfoque encaja bien con los flujos de entrega de apps de Expo, donde el comportamiento de la app puede cambiar con frecuencia y el estado del backend debe mantenerse sincronizado.
Cómo enviar notificaciones desde tu servidor
Una vez que tu backend tiene un Expo Push Token válido, enviar una notificación es sencillo. Lo difícil no es la petición en sí, sino decidir qué incluir en el payload y cuánta confianza depositas en el estado del cliente.
Aquí tienes un ejemplo mínimo al estilo Node usando fetch:
type ExpoPushMessage = {
to: string;
title: string;
body: string;
sound?: 'default' | null;
data?: Record<string, unknown>;
};
export async function sendExpoPushNotification(token: string) {
const message: ExpoPushMessage = {
to: token,
title: 'New review received',
body: 'Tap to open the order details.',
sound: 'default',
data: {
type: 'new_review',
orderId: 'ord_123',
screen: 'OrderDetails',
},
};
const response = await fetch('https://exp.host/--/api/v2/push/send', {
method: 'POST',
headers: {
Accept: 'application/json',
'Accept-encoding': 'gzip, deflate',
'Content-Type': 'application/json',
},
body: JSON.stringify(message),
});
const result = await response.json();
return result;
}
Qué debe hacer cada campo del payload
No uses el payload como cajón de sastre. Que cada campo tenga un propósito.
| Campo | Propósito | Consejos prácticos |
|---|---|---|
to |
Expo Push Token de destino | Valida que pertenezca al registro del dispositivo actual |
title |
Título de la notificación | Mantenlo corto y legible |
body |
Texto principal visible | Haz que la acción sea clara |
sound |
Comportamiento del sonido del sistema | Úsalo con moderación para alertas de alto valor |
data |
Metadatos específicos de la app | Preferir IDs y pistas de ruta sobre contenido enriquecido |
El data object es donde los flujos de producto resultan útiles. Puedes pasar un type y un ID de registro, y luego dejar que la app obtenga los datos más recientes cuando el usuario haga clic. Es más seguro que incrustar blobs grandes o sensibles directamente en el payload.
Mantén los payloads pequeños y aburridos
According to la guía de Courier sobre notificaciones de Expo, los Expo Push Tokens deben tratarse como efímeros, los payloads que superan el límite de tamaño de aproximadamente 4 KB pueden descartarse, y un patrón fiable es enviar payloads de metadatos pequeños como { "type": "new_review", "id": 123 } en lugar de JSON grandes o medios en línea. Ese consejo coincide con lo que funciona en sistemas reales. Los payloads pequeños fallan menos y envejecen mejor cuando cambia la lógica de la app.
Envía suficientes datos para enrutar al usuario. Obtén el resto después de que la app se abra.
Buenas prácticas en el servidor
Una función de envío básica está bien para pruebas. Una de producción suele añadir algunas responsabilidades más:
- Persiste los intentos de envío: Guarda la intención de la notificación con el ID de usuario, token, tipo de payload y marca de tiempo.
- Separa la generación de contenido del transporte: Crea el texto del mensaje en una capa y la solicitud de Expo API en otra.
- Gestiona el feedback de invalidación: Si Expo informa más tarde
DeviceNotRegistered, marca ese token como obsoleto y deja de reintentar a ciegas. - Usa un diseño apto para webhooks: Si tu sistema ya emite eventos, enruta los disparadores de notificaciones a través del mismo tipo de patrón de procesamiento de webhooks en el backend que usas en otras partes.
Antes de depurar los listeners del cliente, envía primero un push de prueba manual. Si un token recibe una notificación simple con un payload diminuto, tu ruta de transporte probablemente está sana. Si no, no empieces cambiando la navegación code. Empieza validando el token, la forma del payload y el estado de permisos.
Manejo de notificaciones entrantes en tu app
La entrega es solo la mitad de la funcionalidad. La app debe hacer algo coherente cuando llega la notificación y cuando el usuario la toca.
Eso significa manejar dos momentos separados:
- la notificación llega mientras la app está abierta
- el usuario interactúa con la notificación desde la bandeja del sistema o la pantalla de bloqueo

El recibo en primer plano y la respuesta del usuario son eventos distintos
Una configuración fiable suele incluir ambos listeners:
import { useEffect } from 'react';
import * as Notifications from 'expo-notifications';
export function useNotificationObservers(
onForegroundMessage: (notification: Notifications.Notification) => void,
onNotificationTap: (response: Notifications.NotificationResponse) => void
) {
useEffect(() => {
const receivedSub = Notifications.addNotificationReceivedListener(
(notification) => {
onForegroundMessage(notification);
}
);
const responseSub = Notifications.addNotificationResponseReceivedListener(
(response) => {
onNotificationTap(response);
}
);
return () => {
receivedSub.remove();
responseSub.remove();
};
}, [onForegroundMessage, onNotificationTap]);
}
addNotificationReceivedListener se ejecuta cuando la app está activa. addNotificationResponseReceivedListener se ejecuta cuando el usuario pulsa una notificación recibida. No los mezcles; sirven para flujos de UX distintos.
Lee el payload de datos y navega con intención
Aquí tienes un patrón práctico para el manejo de taps:
type NotificationData = {
type?: string;
orderId?: string;
screen?: string;
};
export function handleNotificationTap(
response: Notifications.NotificationResponse,
navigation: any
) {
const data =
response.notification.request.content.data as NotificationData;
if (data.screen === 'OrderDetails' && data.orderId) {
navigation.navigate('OrderDetails', { orderId: data.orderId });
return;
}
if (data.type === 'new_review') {
navigation.navigate('Inbox');
return;
}
navigation.navigate('Home');
}
Este patrón se mantiene resiliente porque el payload contiene pistas de enrutamiento, no documentos completos. Si el pedido cambió desde que se envió la notificación, la app puede obtener el estado actual del servidor después de navegar.
Una notificación pulsada debe llevar a un destino obvio. Si tu ruta de fallback es vaga, los usuarios lo notan de inmediato.
Foreground behavior should match user context
Cuando la app ya está abierta, mostrar una alerta del sistema a ciegas puede resultar tosco. A veces lo ideal es un banner in-app, actualizar un badge o hacer un refresh silencioso. Una pantalla de bandeja de entrada de soporte no necesita una alerta visible si el usuario ya está leyendo esa conversación.
Por eso, tu listener de foreground debe bifurcarse según la ruta y el tipo de notificación. Por ejemplo:
- Pantalla de chat abierta: añade el mensaje y evita un banner redundante
- Dashboard abierto: mostrar un toast ligero en la app
- Evento crítico de la cuenta: mostrar un tratamiento de UI más destacado
Un enfoque simple se ve así:
export function handleForegroundNotification(
notification: Notifications.Notification,
currentRouteName: string
) {
const data = notification.request.content.data as { type?: string };
if (currentRouteName === 'ChatThread' && data.type === 'new_message') {
// refresh local thread state
return;
}
// otherwise show your own in-app UI or update badges
}
Si tu app no distingue estos contextos, los usuarios sentirán fatiga por notificaciones más rápido, incluso si la entrega es técnicamente correcta.
Mejores prácticas en producción y errores comunes
La mayoría de las configuraciones rotas de notificaciones push de Expo no fallan porque Expo sea demasiado limitado. Fallan porque los equipos asumen que el token es permanente, que los payloads pueden llevar cualquier cosa y que las actualizaciones de la app no afectarán la lógica de notificaciones.
Esa suposición no sobrevive a producción.

Los tokens son efímeros, no registros de identidad
Un Expo Push Token debe tratarse como un arrendamiento, no como un identificador de dispositivo de por vida. Los tokens pueden rotar tras una reinstalación, cambios en el SO u otros eventos del ciclo de vida. Si un token finalmente devuelve DeviceNotRegistered, tu backend debe dejar de tratarlo como activo.
Un modelo de backend práctico almacena:
- ID de usuario
- plataforma
- metadatos por instalación
- token actual
- timestamp de última conexión
- estado como activo, obsoleto o revocado
No te limites a guardar un solo campo de token en la tabla de usuarios. Los usuarios tienen múltiples dispositivos y el estado de estos cambia.
La estrategia de actualización importa más de lo que admiten tutoriales
La guía oficial del ecosistema deja un vacío operativo importante. El contenido actual sobre notificaciones push de Expo suele omitir cómo mantener la validez del token a través de ciclos de revisión de la App Store y actualizaciones OTA, lo cual es clave para equipos que lanzan cambios en vivo, ya que la fiabilidad de las notificaciones depende del estado del token y la sincronización del backend, como se menciona en Documentación de notificaciones de Expo.
Eso afecta el diseño de tus disparadores de actualización. Momentos ideales para sincronizar el estado del token:
- Lanzamiento de la app tras una actualización
- Inicio de sesión del usuario
- Cambios en la configuración de permisos
- Trabajo de rotación de credenciales en tu proceso de release
- Flujos de recuperación tras tickets de soporte de push
La seguridad y el compliance no deben dejarse para el final del sprint
Muchos tutoriales de Expo se centran en la mecánica y pasan por alto el riesgo operativo. Eso está bien para apps de hobby, pero no para productos fintech, relacionados con la salud o de comercio regulado.
Análisis de Courier para empresas: carencias de notificaciones de Expo destaca la falta de orientación práctica sobre el registro de consentimientos, pistas de auditoría y la minimización de la exposición de payloads sensibles. La conclusión técnica es sencilla:
- No incluyas datos de negocio sensibles en el texto de la notificación o en los metadatos del payload.
- Registra los cambios de consentimiento en el servidor.
- Registra qué intención de notificación se envió a qué token.
- Usa IDs en los payloads y obtén contenido protegido tras abrir la app.
Para equipos que alinean las operaciones de lanzamiento con un enfoque app stores y las prácticas de seguridad de API, push debe incluirse en la misma disciplina de revisión que auth, analytics y el registro de eventos del backend.
Las notificaciones push son mensajes para el usuario, pero también son un problema de sistemas distribuidos. Trátalas con el mismo cuidado que aplicas al estado de autenticación y a los eventos de pago.
Lo que suele funcionar y lo que suele fallar
| Suele funcionar | Suele fallar |
|---|---|
| Solicitar permiso tras una explicación clara de su valor | Mostrar el prompt en el primer frame |
| Probar en dispositivos reales | Confiar en el registro del simulador |
| Almacenar tokens con contexto del dispositivo | Un token por registro de usuario |
| Enviar payloads de metadatos pequeños | Incrustar blobs grandes o sensibles |
| Manejar eventos de primer plano y tap por separado | Asumiendo que todas las notificaciones siguen un solo camino |
| Expirar tokens obsoletos de forma agresiva | Reintentar tokens muertos para siempre |
Una configuración sólida de Expo push no es complicada. Es disciplinada.
Si tu equipo publica cambios frecuentes en la lógica de la app y necesita un control más estricto sobre el comportamiento de las releases, los rollbacks y la visibilidad de la entrega, Capgo is worth a look. It helps mobile teams push updates quickly without waiting on store review, which is especially useful when notification flows, routing logic, or client-side fixes need to reach users fast.