Cómo añadir notificaciones push de OneSignal a una app web creada con Codex y WebViewGold
El push es la función nativa en la que App Review se fija primero, y la mayor parte del trabajo está en tu app web y en tu backend, así que es una buena tarea para Codex. WebViewGold ya incluye el SDK de OneSignal en los dos proyectos nativos; Codex construye el momento del opt-in, sincroniza con tu servidor el ID de suscripción de cada dispositivo y escribe el endpoint que envía notificaciones segmentadas. Aquí tienes el reparto de tareas, las reglas para AGENTS.md y un prompt que evita los errores clásicos.
Desde $109 de pago único por plataforma, o deja que nuestro equipo lo haga todo desde $698.
Garantía de aprobación – o te devolvemos el dinero en 14 días
Sin programar
Pago único, actualizaciones gratuitas
Soporte humano 24/7
Hecho en Alemania desde 2014 · ISO 27001
9:41
Tu app
Tu appYa en tu bolsillo
App Store · Google Play Aprobada
El objetivo y lo que ve el revisor
El objetivo es un push dirigido a cada usuario: "Nueva respuesta a tu comentario" en el móvil de una persona, no un envío masivo a todos los que instalaron la app. Para eso hacen falta tres piezas: la app nativa registrada en OneSignal, un servidor que sepa qué suscripción de OneSignal pertenece a qué usuario y una llamada de envío que abra la pantalla correcta dentro de la app cuando alguien toca la notificación.
El revisor ve menos de todo esto de lo que crees, pero lo ve pronto. Un diálogo de permisos que aparece antes de que se haya pintado la primera pantalla parece una web que pide permisos sin motivo. Además, la Directriz 4.5.4 de Apple dice que el push no puede ser obligatorio para que la app funcione, y los pushes promocionales necesitan un opt-in explícito en tu propia interfaz y una forma de darse de baja. Por tanto: pregunta después de una acción con sentido, explica qué va a recibir la gente y deja un interruptor en los ajustes. Hecho así, el push es una de las señales más claras de funcionalidad nativa según la Directriz 4.2 (qué exige la 4.2 a las apps basadas en web).
Esta guía parte de un front end en React, una API en Express y Firestore, con el helper de user agent isNativeApp() de la plantilla de AGENTS.md para WebViewGold. El patrón sirve para cualquier stack.
Quién hace qué
Pieza
Dónde está
Quién
App de OneSignal con credenciales de APNs (iOS) y FCM (Android)
Panel de OneSignal
Tú, una sola vez
OneSignal activado en las apps nativas
iOS: kPushEnabled en true y oneSignalID en Config.swift. Android: PUSH_ENABLED en true en Config.java y el App ID en ONE_SIGNAL_APP_ID en build.gradle
Tú, o Codex en el proyecto nativo
Aviso del primer arranque desactivado
askforpushpermissionatfirstrun en false (Config.swift), requirePush en false (Config.java)
Tú
Panel de opt-in que lanza el diálogo nativo
App web, navegando a registerpush://
Codex
ID de suscripción a tu servidor
getonesignalplayerid:// rellena onesignalplayerid; o activa autoInjectVariable / AUTO_INJECT_VARIABLES; o recibe ?onesignal_push_id= en la primera URL mediante kPushEnhanceUrl / PUSH_ENHANCE_WEBVIEW_URL
Codex (web y Express)
Estado en los ajustes
checkNativePushPermissions:// rellena nativePushEnabled; settingsapp:// abre los ajustes del sistema de la app
Codex
Envío
Express llama a la API REST de OneSignal
Codex, y tú lo revisas
La sección de AGENTS.md para el push
Añade esto al final de tu AGENTS.md. Codex lo lee al empezar cada sesión, y el apartado Review guidelines es lo que sigue la revisión de código de Codex cuando comentas @codex review en un pull request. Cada esquema y cada clave salen de la documentación de push para iOS y de la documentación de push para Android. Puedes dejarla en inglés: Codex la entiende igual de bien.
markdown
## Push notifications (OneSignal via WebViewGold)
- Native side: OneSignal is enabled in the WebViewGold projects
(iOS: kPushEnabled + oneSignalID; Android: PUSH_ENABLED + ONE_SIGNAL_APP_ID).
The first-run prompt is OFF (askforpushpermissionatfirstrun / requirePush = false).
- Never add the OneSignal Web SDK, a web push service worker or
Notification.requestPermission(). In the app, permission is requested only by
navigating to registerpush://, always behind isNativeApp().
- Permission status: checkNativePushPermissions:// sets window.nativePushEnabled
(asynchronously). If false, offer a button that navigates to settingsapp://.
- Device identity: getonesignalplayerid:// sets window.onesignalplayerid
(asynchronously; may be empty before the user allows push). Poll with a timeout.
On iOS, combine data calls: multiapicall://getonesignalplayerid,getappversion
- Storage: pushDevices/{subscriptionId} = { uid, platform, updatedAt }.
One user can have many devices. Delete the row on logout.
- Sending happens only on the server (server/push/send.js), with ONESIGNAL_API_KEY
from the environment. Deep links go in data.url (opens inside the app).
Never set OneSignal's top-level url field: that is the Launch URL and opens the browser.
- Promotional pushes need explicit opt-in in our UI and an opt-out in settings.
## Review guidelines
- P1: ONESIGNAL_API_KEY, or any call to api.onesignal.com, in client code.
- P1: a notification payload with a top-level url instead of data.url.
- P1: registerpush:// or getonesignalplayerid:// without an isNativeApp() check.
WebViewGold nos ha permitido el tiempo de mercado más corto y eficiente que jamás sea posible de todas las otras plataformas de aplicaciones que hemos probado durante los últimos 5 años. No sé por qué no lo había descubierto antes, ¡pero estoy muy, muy contento de haberlo hecho. Actualmente cerca de 6000 descargas de aplicaciones tanto en iOS App Store & Google Play Store sin ningún problema!
Inicia Codex en el repositorio con permiso de escritura en el espacio de trabajo y aprobaciones bajo demanda: codex --sandbox workspace-write --ask-for-approval on-request. En ese modo la red está desactivada por defecto, lo que encaja con esta tarea: debe producir código y tests con mocks, no notificaciones reales. Sustituye las partes entre corchetes.
Pégalo en la Codex CLI o en la extensión del IDE
Lee primero AGENTS.md, sobre todo el puente de WebViewGold y la sección de push. Después implementa las notificaciones push de principio a fin en este repositorio. No toques los proyectos nativos.
Contexto: las apps nativas de iOS y Android usan OneSignal a través de WebViewGold. El aviso de permisos del primer arranque está desactivado (askforpushpermissionatfirstrun = false en Config.swift, requirePush = false en Config.java), así que es nuestra app web la que decide cuándo preguntar.
1. Opt-in: después de que el usuario [cree su primer proyecto], muestra un panel breve que explique qué notificaciones va a recibir ([respuestas, recordatorios]), con los botones Activar y Ahora no. Activar navega a registerpush:// cuando isNativeApp() es true. Recuerda el Ahora no durante 14 días. No muestres nunca el panel en navegadores.
2. Sincronización de la suscripción: después del login y después del opt-in, navega a getonesignalplayerid:// y espera a window.onesignalplayerid (consulta cada 200 ms, abandona a los 5 s, ignora los valores vacíos). Envíalo por POST junto con la plataforma a /api/push/devices, autenticado con el ID token de Firebase. El servidor guarda pushDevices/{subscriptionId} = { uid, platform, updatedAt }, de modo que un móvil que cambia de manos pasa al nuevo usuario. Al cerrar sesión, llama a DELETE /api/push/devices/:id antes de hacer el sign-out.
3. Ajustes: una fila de Notificaciones. Navega a checkNativePushPermissions:// y lee window.nativePushEnabled tras un breve retraso. Si es false, muestra "Las notificaciones están desactivadas" y un botón que navegue a settingsapp://.
4. Envío: server/push/send.js, que exporte sendPushToUser(uid, { title, body, path }). Carga los pushDevices del usuario, llama a la API REST de OneSignal con include_subscription_ids y pone el deep link (nuestro origen de producción + path) en data.url. No uses nunca el campo url de nivel superior. Lee ONESIGNAL_APP_ID y ONESIGNAL_API_KEY del entorno y añade ambas a .env.example sin valores. Llámala desde [el handler de nuevas respuestas].
5. Tests: tests unitarios para las rutas de dispositivos (autenticación obligatoria, upsert, borrar solo filas propias) y para sendPushToUser con fetch simulado. No llames a la API real de OneSignal.
6. Termina con: archivos modificados, variables de entorno que debo configurar y una lista numerada de pruebas en dispositivo.
Cómo debería ser la parte del servidor
Úsalo como referencia cuando leas el diff. Hay dos líneas que importan más que el resto: la clave de API nunca sale del servidor y el deep link va en data.url. El url de nivel superior de OneSignal es la Launch URL, que abre un navegador; la documentación de WebViewGold pide expresamente el campo de datos adicionales url en su lugar.
javascript
// server/push/index.js (Express + firebase-admin, Node 18+ for global fetch)
import express from "express";
import { getFirestore, FieldValue } from "firebase-admin/firestore";
import { requireUser } from "../auth/requireUser.js"; // verifies the Firebase ID token, sets req.uid
const db = getFirestore();
export const pushRouter = express.Router();
// One row per device. The document ID is the OneSignal subscription ID, so a phone
// that changes hands (logout, login as someone else) simply moves to the new user.
pushRouter.post("/api/push/devices", requireUser, express.json(), async (req, res) => {
const { subscriptionId, platform } = req.body || {};
if (!subscriptionId) return res.status(400).json({ error: "subscriptionId required" });
await db.collection("pushDevices").doc(subscriptionId).set({
uid: req.uid, platform: platform || "unknown", updatedAt: FieldValue.serverTimestamp()
});
res.status(204).end();
});
pushRouter.delete("/api/push/devices/:id", requireUser, async (req, res) => {
const ref = db.collection("pushDevices").doc(req.params.id);
const snap = await ref.get();
if (snap.exists && snap.get("uid") === req.uid) await ref.delete();
res.status(204).end();
});
export async function sendPushToUser(uid, { title, body, path }) {
const devices = await db.collection("pushDevices").where("uid", "==", uid).get();
const ids = devices.docs.map((d) => d.id);
if (ids.length === 0) return 0;
const res = await fetch("https://api.onesignal.com/notifications?c=push", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Key " + process.env.ONESIGNAL_API_KEY // server only
},
body: JSON.stringify({
app_id: process.env.ONESIGNAL_APP_ID,
target_channel: "push",
include_subscription_ids: ids,
headings: { en: title },
contents: { en: body },
// WebViewGold opens this inside the app. Not the top-level "url" (Launch URL).
data: { url: "https://app.example.com" + path }
})
});
if (!res.ok) throw new Error("OneSignal " + res.status + ": " + (await res.text()));
return ids.length;
}
De la configuración a la primera notificación en un dispositivo
Reserva una tarde; casi todo es esperar a que terminen las compilaciones.
Crea la app de OneSignal
Añade la plataforma Apple iOS con tus credenciales de APNs y la plataforma Google Android con el JSON de la cuenta de servicio de Firebase que pide OneSignal. Copia el App ID y la App API key. La clave va en el entorno de tu servidor, nunca en AGENTS.md, en un entorno de tareas en la nube que compartas ni en el bundle del cliente.
Activa OneSignal en WebViewGold
iOS: kPushEnabled en true, tu App ID en oneSignalID, askforpushpermissionatfirstrun en false y la capability Push Notifications activada para tu bundle ID. Android: PUSH_ENABLED en true, el App ID en ONE_SIGNAL_APP_ID en build.gradle y requirePush en false. Config.swift y Config.java son texto plano, así que una segunda sesión de Codex en el proyecto de WebViewGold puede poner exactamente estos valores; la firma sigue siendo cosa tuya.
Lanza la tarea web y revísala
Pega el prompt de arriba. Antes de aceptar el resultado, escribe /review en la CLI para una segunda pasada sobre los cambios sin confirmar y después lee tú mismo el git diff con las reglas de AGENTS.md al lado. Busca primero las dos reglas P1.
Despliega y configura el entorno
Define ONESIGNAL_APP_ID y ONESIGNAL_API_KEY en el servidor y despliega la app web y la API. Los proyectos nativos no cambian en este paso, y esa es la ventaja de dejar la lógica en el lado web.
Compila para dispositivos reales
TestFlight para iOS y una pista de pruebas internas para Android. El push necesita una build firmada en hardware real. ¿No tienes un Mac a mano? El Cloud Builder configura las mismas opciones de WebViewGold en un formulario del navegador y compila las dos apps.
Envía dos veces
Primero desde el panel de OneSignal con un campo de datos adicionales url, lo que demuestra que el lado nativo funciona. Después dispara tu propio evento (una respuesta, un recordatorio), lo que demuestra que funcionan el servidor, la asignación de dispositivos y el deep link.
Checklist de pruebas en dispositivo
Instalación limpia: ningún diálogo de permisos al abrir la app.
El panel de opt-in aparece después de la acción que lo dispara; Activar muestra el diálogo nativo; Ahora no se recuerda.
Tras permitirlo, el ID de suscripción de este dispositivo queda guardado para el usuario con sesión iniciada; un segundo dispositivo añade una segunda fila.
Cierra sesión e inicia sesión con otro usuario en el mismo móvil: la fila pasa al nuevo usuario y el anterior ya no recibe nada en este móvil.
Deniega el permiso y abre los ajustes en la app: el estado aparece como desactivado y el botón abre los ajustes del sistema mediante settingsapp://.
Toca una notificación con la app cerrada y con la app en segundo plano: la pantalla de destino se abre dentro de la app, no en Safari ni en Chrome.
Android 13 o posterior: aparece el diálogo del sistema; nativePushEnabled solo es true cuando están concedidos tanto el ajuste de notificaciones como el permiso en tiempo de ejecución.
Errores que comete Codex sin las reglas
Añadir el Web SDK de OneSignal, un service worker o Notification.requestPermission(). El web push es el canal equivocado dentro de la app; WebViewGold ya incluye el SDK nativo.
Leer window.onesignalplayerid justo después de la navegación. Las variables inyectadas llegan de forma asíncrona, así que consulta con un timeout.
Lanzar varias llamadas al puente seguidas en iOS, donde no se ejecutan todas. Combina las llamadas de datos con multiapicall://getonesignalplayerid,getappversion (consulta la documentación de MultiAPICall).
Guardar un solo ID de push por usuario. La gente tiene un móvil y una tablet; guarda una fila por suscripción.
Poner el deep link en el campo url de nivel superior, que manda a los usuarios al navegador.
Conectar registerpush:// sin tener activado ningún servicio push en la configuración nativa. No pasa nada y el botón parece roto.
Preguntas frecuentes
¿Puedo usar Firebase Cloud Messaging o Pushwoosh en lugar de OneSignal?
Sí. WebViewGold es compatible con OneSignal, Firebase y Pushwoosh. Para Firebase, activa kFirebasePushEnabled (iOS) o FIREBASE_PUSH_ENABLED (Android), añade los archivos de servicio de Google y lee el token con getfirebaseplayerid://, que rellena firebaseplayerid. Las reglas de AGENTS.md siguen siendo las mismas; solo cambian los nombres de las variables y la API de envío.
¿Puede Codex probar las notificaciones push en su sandbox?
No de principio a fin. Un push necesita una build firmada en un dispositivo real y credenciales reales. Codex puede hacer tests unitarios de las rutas de dispositivos y de la función de envío con fetch simulado, lo que detecta la mayoría de los errores; el último paso es tu prueba en el dispositivo.
¿Por qué guardar IDs de suscripción en lugar de usar los external IDs de OneSignal?
Porque el puente de WebViewGold entrega a tu app web el ID de suscripción (getonesignalplayerid:// o la variable inyectada), así que ese es el identificador con el que puedes contar. Asignar los IDs de suscripción a tu propio ID de usuario también hace que el cierre de sesión, el cambio de cuenta y la eliminación de la cuenta sean fáciles de hacer bien.
¿Debería dejar activado el aviso de permisos del primer arranque?
Solo si el push es el núcleo de la app desde el primer segundo, por ejemplo en una app de alertas. En la mayoría de las apps, preguntar después de una acción con sentido consigue que más gente diga que sí y se parece menos a una web pidiendo permisos.
¿Basta con el push para pasar la Directriz 4.2?
Ayuda, pero ninguna función por sí sola garantiza la aprobación. Los revisores juzgan la experiencia completa: un diseño móvil limpio, una navegación propia de una app, la gestión del modo sin conexión y detalles nativos como el push y la función de compartir.
Tres formas de llevar tu app a las tiendas
El mismo resultado, con distinta cantidad de trabajo por tu parte. Las tres incluyen la garantía de aprobación y soporte humano 24/7.
Hazlo tú mismo
$109 por plataforma, pago único
La licencia de WebViewGold: un proyecto de Xcode o Android Studio listo para usar. Pega tu URL, define nombre e icono, activa funciones, compila y envía. Actualizaciones gratuitas para siempre.
Sin Mac, sin Xcode, sin Android Studio: configura en el navegador, compilamos ambas apps en la nube y las entregamos a App Store Connect y a Play Console.
Envíanos tu URL y tu icono. Nuestro equipo configura, compila y entrega tu app en 72 horas, en marca blanca y con los proyectos fuente. Ambas plataformas por $1,158.
Los reembolsos se conceden bajo las condiciones indicadas en nuestras FAQ: un rechazo por motivos ajenos al contenido por parte de Apple y/o Google, reclamado dentro de los 14 días posteriores a la compra.