En la web abierta, al tocar un Universal Link el sistema operativo tiene la oportunidad de abrir tu app; un App Link en Android hace lo mismo. Dentro de un WebView, ese traspaso muere. Cuando un WebView carga una URL por sí mismo, el sistema operativo nunca pregunta qué app debe manejarla, por lo que los archivos de asociación que publicaste (apple-app-site-association / assetlinks.json) nunca se consultan. Para una app fintech, eso significa que la redirección que debería llevar al usuario a la app del banco para la autenticación fuerte 3DS/SCA, o a una wallet para confirmar un pago, aterriza en silencio en una página web — o a ninguna. Un runtime propiedad del host puentea la apertura nativa para que el traspaso funcione en todos los motores.
Los Universal Links nunca se disparan dentro de un WebView
Los Universal Links y los App Links de Android son un mecanismo a nivel del sistema. Cuando un usuario toca un enlace en Safari, Chrome o el navegador del sistema, iOS o Android comprueban el archivo de asociación del dominio y, si hay una app instalada que coincida, la abren. La intercepción ocurre antes de que cualquier navegador renderice la página. Pero cuando un WebView carga esa misma URL, la app host ya tiene una vista web abierta y simplemente la renderiza dentro de ella. El sistema nunca se consulta, el archivo de asociación es irrelevante y la app nunca se lanza. Apple lo documenta directamente: instanciar un WKWebView para manejar un Universal Link hace que iOS abra el sitio web en Safari en lugar de tu app; el enlace solo abre la app si el usuario lo toca desde una vista ya embebida — y aun así el host debe actuar. Las redirecciones por JavaScript (window.location.href) no cuentan como navegación iniciada por el usuario, así que el resolvedor del sistema también las ignora.
Los esquemas URI personalizados también se suprimen
La salida antigua — un esquema URI personalizado como myapp:// — tampoco es fiable dentro del WebView. En iOS no existe una forma soportada para que una página dentro de un WKWebView fuerce al host a ceder el control; un esquema personalizado se suprime en la mayoría de los contextos de webview y falla en silencio sin red de respaldo. En Android, un WebView puro tampoco resuelve App Links; solo las Android Custom Tabs (una instancia de navegador real) lo hacen. Un host puede sobreescribir shouldOverrideUrlLoading en Android o implementar decidePolicyFor navigationAction en iOS para llamar a UIApplication.open, pero la mayoría de las apps —especialmente las redes sociales y las super-apps que envuelven contenido de terceros— no lo hacen, porque mantener al usuario dentro de la vista web es el punto. El resultado es el mismo en todas partes: el salto entre apps del que depende la página no ocurre.
Qué se rompe cuando muere el traspaso
- La redirección 3DS/SCA a la app del banco falla. La autenticación fuerte a menudo delega en la app del banco emisor. Dentro de un WebView ese salto se traga, así que la autenticación se atasca o vuelve en silencio a un respaldo web que el banco nunca terminó.
- La confirmación de wallet y pagos se rompe. Los enlaces "confirma en tu wallet" que deberían enlazar a Google Pay, Apple Pay o un banco simplemente no hacen nada.
- Los callbacks de vinculación de cuenta y OAuth fallan. Los flujos "abre la app autenticadora/banco para aprobar" aterrizan en una página en blanco o sin salida.
- Se pierden la atribución y los traspasos de instalación. Los deep links diferidos que deberían sobrevivir una instalación y llevar al usuario a la pantalla correcta se rompen, dañando los embudos de activación.
La solución del runtime
Un runtime propiedad del host es dueño del traspaso entre apps. La página llama a una API uniforme —runtime.openApp(url)— y el host la resuelve mediante el mecanismo nativo de la plataforma: UIApplication.open en iOS (con el Universal Link o un esquema que el host valida) y un Intent / App Link en Android, con respaldo web cuando no hay app instalada. El runtime también puede detectar que está dentro de una vista embebida y mostrar un botón explícito "Abrir en el navegador", en lugar de un enlace que parece no hacer nada. Una integración, todos los motores, un traspaso real al final.
| Dimensión | WebView | Runtime propiedad del host |
|---|---|---|
| Resolución de enlace por el SO | Nunca se consulta | El host llama a apertura nativa |
| Universal / App Links | Se tragan | Se respetan vía apertura nativa |
| Esquema URI personalizado | Suprimido / fallo silencioso | Traspaso validado por el host |
| Salida en iOS | Ninguna fiable | UIApplication.open |
| Salida en Android | Solo Custom Tabs | Intent / App Link |
| Traspaso 3DS / wallet | Se rompe | Funciona |
| Respaldo sin app | Ninguno | Respaldo web explícito |
Una app nativa puede abrir otra app en todos los dispositivos. Un WebView puro, en casi ninguno. Un runtime le da a la página una sola llamada — y un traspaso que realmente aterriza.
Fuentes
- Apple Developer — Soporte de Universal Links — instanciar un WKWebView para manejar un Universal Link abre el sitio en Safari en lugar de la app; el resolvedor del sistema se omite.
- Apple Developer — UIApplication.open — la API nativa que el host debe llamar para enrutar una URL fuera del WebView a otra app.
- Deeplinkly — Deep links en navegadores in-app — el WebView carga la URL por sí mismo, así que el sistema nunca resuelve; Universal Links y App Links nunca llegan a la app que los declara.
- LinkRunner — Por qué los Universal/App Links se rompen en navegadores in-app — WKWebView no dispara la intercepción de Universal Links; las redirecciones JS no son iniciadas por el usuario y se ignoran.
- Tolinku — URI Schemes vs Universal Links — dentro de tu app (WKWebView / SFSafariViewController) los Universal Links están deshabilitados por defecto; debes usar
UIApplication.shared.open().