30 min de lecturaPor Sergey Matrosov
Recorrido del usuario y embudo multicanal de la promoción de apps con datos de AppsFlyer.
Antes de empezar, un aviso: hay menos datos compartidos por culpa del RGPD y de iOS 14.5, así que el resultado no serán datos consistentes.
Hay al menos dos trayectorias para seguir el recorrido del usuario: dentro de un producto, que es el terreno de la analítica de producto, y a través de los canales de tráfico, que es el terreno de la analítica de marketing. A esta última se la llama embudo multicanal. Hoy existen la promoción web y la de apps, así que hay tres embudos multicanal 1: el de la web, el de las apps y el de la combinación de ambos.
Hoy vamos a construir un recorrido de usuario y un embudo multicanal con datos de AppsFlyer para la captación de instalaciones. El resultado será un embudo de las fuentes de la app, las llamadas media_sources, que captaron a los usuarios. Estos son los resultados finales:
- Recorrido del usuario:

- Embudo multicanal:

El problema es que hacerlo es realmente difícil, por varias complicaciones ligadas a la medición de la promoción de apps.
Medición en web
Recordemos la medición en web, que es bastante directa:

Con un UTM puedes medir casi todas las fuentes online 2.
Medición en app
La medición en app tiene dos puntos intermedios adicionales:

Uno de los problemas es que no puedes obtener información directamente de la App Store. Los Mobile Measurement Partners (MMP) como AppsFlyer sí pueden, y tienes que usarlos para medir las fuentes.
¡La disponibilidad de los datos en bruto depende de tu plan de suscripción! Por ejemplo, hay tablas exclusivas de DataLocker 3 y del almacenamiento de informes en bruto de AppsFlyer, disponibles como función premium adicional: clicks, impressions, clicks retargeting, impressions retargeting, sessions, retargeting sessions, cross-platform engagements.
Por desgracia, tampoco toda la información está disponible para los MMP, y hay varios límites.
- AppsFlyer puede guardar el canal que originó la instalación y solo los tres últimos contribuidores a esa instalación en la tabla
install, pero si uno de los contribuidores era FB (por ejemplo, el contribuidor 1), no obtendrás nada 4 en esa columna. - Hay datos marcados como
restricteddentro de los datos en bruto 7 de la tablainstall. Ocurre, por ejemplo, con Facebook. Existe una forma de sortearlo en cierta medida en Android, llamada Google Install Referrer8. Además, si la media_source esrestricted, también se borran todos los datos sobre los contribuidores (asistencias) a la instalación. Si el contribuidor está en la lista de fuentes restringidas, el valor del campo es null (es decir, no todos los null en los datos de contribuidores significan que no haya contribuidores). - El propio AppsFlyer tiene problemas con su atribución porque en algunos casos le faltan identificadores del contacto con los partners. Por eso tiene varias maneras de atribuir instalaciones, eventos y fuentes de retargeting a una media_source concreta, por ejemplo un método probabilístico. Hablaremos más de esto al ver la tabla
install. - RGPD, iOS 14.5, Android 12: en cuanto los usuarios rechazan los anuncios personalizados, los ID de app (IDFA en Apple, GAID en Android) se registran como nulos o como una cadena de ceros, lo que imposibilita cruzarlos con la tabla
click. - Si un usuario de iOS 14.5 no concede el permiso de App Tracking Transparency (ATT), se registrará como
“organic”. - Que la media_source sea
“organic”no siempre significa que el usuario abriera Google Play o la App Store, buscara la app y la instalara sin hacer clic en ningún anuncio. AppsFlyer marca todo como“organic”siempre que no puede determinar la fuente o si la ventana de atribución de la fuente ha terminado.
Sí, puedes enriquecerlo con datos de la tabla click, pero no habrá datos de SRN como Google, FB, etc. 5 6 La buena noticia, según nuestra experiencia, es que la fórmula «un canal, una instalación» funciona la mayor parte del tiempo, y si hay muchos contribuidores a la instalación, puedes estar bastante seguro de tener los datos completos de todos los puntos de contacto previos a la instalación el 95 % de las veces.
En resumen: incluso unos datos de AppsFlyer bien unidos estarán muy fragmentados. La pregunta clave es, por seguir con la analogía, si puedes beber de una taza rota que se ha recompuesto con solo la mitad de sus trozos.
-
Las particularidades de la medición del contacto con los canales:
-
La más importante: todos los contactos con canales (media_sources) se atribuyen a los eventos que las apps envían a AppsFlyer, sobre todo cuando hablamos de campañas de retargeting. No hay un evento por defecto de AppsFlyer, como
install, cuando intentamos identificar el contacto de las campañas de retargeting. Imagina que la instalación de tu app vino de la fuentelastclickcityy lanzaste una campaña de retargeting engooglesobre usuarios activos. En la app mides (con tu propio medidor) un único evento:“registration”. Si no envías el evento“registration”a AppsFlyer y el usuario hizo unregistration, no verás ninguna fila con la fuentegoogle: -
Si envías ese evento pero no marcaste la campaña de google (
is_retargeting_true) como campaña de retargeting dentro de AppsFlyer,“registration”se atribuirá a la fuente de instalación. -
Si no hay ninguna campaña de retargeting, de modo que solo hubo una campaña de instalación, y el usuario dispara un evento
“registration”, se atribuirá a la fuente de instalación.
| appsflyerid | media_source | event | event_time |
|---|---|---|---|
| 111-111 | lastclickcity | install | 2021-04-08 |
(Sin filas con el evento de login = sin filas con la fuente «google»)
Como contacto = disparo de un evento (= engagement 9), por defecto no hay medición de la fuente. No lo olvides 10. Sí, en AppsFlyer existe la sesión, pero no existe la fuente de la sesión, y solo está disponible en DataLocker. Solo los eventos pueden revelar un contacto: sin evento no hay contacto del canal. Eso sí, no puedes enviar todos (¡sic!) los eventos a AppsFlyer. Hablaremos más de esto después.
| appsflyerid | media_source | event | event_time |
|---|---|---|---|
| 111-111 | lastclickcity | install | 2021-04-08 |
| 111-111 | lastclickcity | registration | 2021-04-09 |
En algunos casos puede ser null.
No olvides marcar una campaña de retargeting como retargeting para conseguir esto:
| appsflyerid | media_source | event | event_time |
|---|---|---|---|
| 111-111 | lastclickcity | install | 2021-04-08 |
| 111-111 | registration | 2021-04-09 |
Hay más información sobre la atribución de campañas de retargeting, con ilustraciones, aquí.
| appsflyerid | media_source | event | event_time |
|---|---|---|---|
| 111-111 | lastclickcity | install | 2021-04-08 |
| 111-111 | lastclickcity | registration | 2021-04-09 |
Nosotros proponemos cambiar esa segunda fila a direct/none.
| appsflyerid | media_source | event | event_time |
|---|---|---|---|
| 111-111 | lastclickcity | install | 2021-04-08 |
| 111-111 | direct/none | registration | 2021-04-09 |
-
Quizá te preguntes por qué no enviar a AppsFlyer todos los eventos que mides en tu app. La respuesta es que no siempre es gratis. Y porque AppsFlyer no lo es
- Existe un plan de suscripción basado en eventos para el sector de los juegos, y por eso en ese caso solo debes enviar los eventos que de verdad importan para marketing.
- En el resto de casos pagas por las instalaciones no orgánicas, las reatribuciones (instalaciones repetidas) y los re-engagements (muy ligados al retargeting11). Es decir, no deberías saturar AppsFlyer con eventos.
-
Si quieres combinar los datos de app y de web sobre los usuarios, haz que la zona no autenticada de tu app sea lo más corta posible. Empuja a los usuarios a registrarse la primera vez que abren la app. Así podrás darles un ID de usuario (
customer_user_id), que es la clave para unir los datos de app y de web. No se puede hacer conappsflyer_id(el equivalente del client ID de GA, pero para apps) porque esa dimensión no se puede implementar en web; su ámbito es solo la app. -
Revisa las ventanas de inclusión en la interfaz de AppsFlyer: son las clásicas ventanas de conversión retroactiva para impresiones y clics.
-
También tienes que comprobar si hay ventanas de exclusión: tiempo mínimo entre conversiones de re-engagement y periodo mínimo de inactividad.
- Tiempo mínimo entre conversiones de re-engagement: por defecto, la ventana de re-engagement existente se cierra y se registra otro re-engagement; los eventos in-app posteriores se atribuyen al re-engagement más reciente. Para evitarlo, fija el tiempo mínimo entre conversiones de re-engagement. Así, solo cuando haya pasado ese tiempo mínimo podrá registrarse otro re-engagement.
- Periodo mínimo de inactividad: al fijar un tiempo mínimo de inactividad, AppsFlyer no atribuye usuarios activos a campañas de retargeting = todos los eventos se registrarán en la media source de la instalación. Por ejemplo, si el límite mínimo de inactividad es de tres días, los usuarios activos en los 0-3 días previos no se atribuyen.
Recomendaciones sobre la medición en app si tienes un plan de suscripción basado en eventos
Te sugerimos hacer lo siguiente con AppsFlyer:
-
Piensa muy bien la arquitectura de eventos dentro de AppsFlyer: sigue estas recomendaciones.
-
Envíale solo eventos de marketing.
-
Retargeting:
-
Si haces una campaña de retargeting, configúrala como retargeting en AppsFlyer. Si no, los eventos se atribuirán a la fuente o campaña de instalación.
-
Si es para usuarios no registrados:
- Llévalos a una pantalla no autenticada que no sea la principal 12, creada específicamente para campañas de retargeting de este tipo (para usuarios no registrados).
- ¡Asegúrate de que ningún otro usuario pueda llegar a ella desde otras pantallas de la app!
- Configura un evento
“open”de AppsFlyer para identificar el canal.
-
Si es para usuarios registrados:
- Llévalos a una pantalla autenticada creada específicamente para campañas de retargeting de este tipo (para usuarios no registrados).
- ¡Asegúrate de que ningún otro usuario pueda llegar a ella desde otras pantallas de la app!
- Configura un evento
“open”de AppsFlyer para identificar el canal.
Si configuras un evento «open» para todas las pantallas, o incluso solo para la principal, dispararás muchísimo el volumen de datos en bruto.
-
-
Reduce las zonas autenticadas para obtener el user_id cuanto antes.
-
Enriquece los datos de eventos atribuibles a los canales que registra AppsFlyer usando GA4, unido a AppsFlyer por el user_id. 13
-
No compartas la dev.key con nadie de fuera de tu empresa: hay riesgo de recibir spam de eventos.
Tablas de datos en bruto de AppsFlyer
Insistimos: ¡la disponibilidad de los datos en bruto depende de tu plan de suscripción!Puedes obtener los datos en bruto así 14:
- Página de exportación de datos en bruto desde la interfaz
- Pull API y Push API
- DataLocker: esto también depende de tu plan. Si es tu caso, verás el flujo de datos configurado hacia BQ desde ahí. No todas las tablas se pueden representar en BQ desde el principio.

Veamos ahora qué obtenemos de AppsFlyer con DataLocker en nuestra base de datos. Con 9 tablas principales 15 basta para construir un recorrido de usuario:
clicks
installs
post-attribution installs
inapps
clicks_retargeting
inapps_retargeting
post-attribution in-apps
organic_uninstalls
uninstalls
Aquí tienes un esquema de cómo se conectan la mayoría de estas tablas que representan el recorrido del usuario:

- t1, t2, etc., significan «touch» (contacto).
- c1, c2, etc., significan «contributor» (contribuidor).
- e1, e2, etc., significan «event» (evento).
- Una línea de puntos representa la atribución de AppsFlyer.
- Los datos de SRN solo están en las tablas «installs» e «inapps» + «inapps_retarg».
Hay 4 tablas adicionales:
organic_uninstalls
uninstalls
post-attribution installs
post-attribution in-app events
Las dos últimas son informes de la herramienta Protect360, que ofrece protección contra el fraude.
post-attribution installs: Protect puede cambiar la media source de la instalación durante los 7 días posteriores a la instalación (8 días en total) a organic o a uno de la lista de contribuidores 16 a la instalación. La media source correcta estará en la columna rejected_reason_value 17. La mayoría de las veces es null u organic.
post-attribution in-app events: Protect puede cambiar la media source de los eventos durante los 30 días posteriores a la instalación. La media source correcta estará en la columna rejected_reason_value. La mayoría de las veces es null u organic.
Hemos construido nueve tablas de ejemplo con el número de columnas necesario para que reproduzcan los datos tal y como vienen en los datos en bruto reales de AppsFlyer.
Información sobre los usuarios
Hay seis usuarios con trayectorias distintas, y cada uno tiene su propia particularidad.
100-200 tiene una media_source de instalación fraudulenta y una campaña de retargeting fraudulenta. Tenemos que cambiar ambas usando las tablas de Protect360.
111-222 tiene una hora de instalación distinta en las tablas clicks e inapps. Usaremos la hora de instalación de inapps porque es anterior. También hay un contacto duplicado en clicks e installs por la fuente mobtop. Lo desduplicaremos con DISTINCT.
333-444 tiene una reinstalación procedente de la campaña de retargeting.
777-888 tiene una media_source de instalación fraudulenta y no tiene la media source correcta, así que hay que eliminar a este usuario.
999-100 tiene una campaña de retargeting fraudulenta y no tiene la media source correcta para ella, así que hay que eliminar la fila de la campaña de retargeting de este usuario.
Tablas
Hemos intentado usar todos los campos de datos tal cual 18.
Clicks
Son clics en anuncios pero sin las SRN, como Google, Facebook, etc. 19 Recuerda: si un usuario ya ha instalado tu app pero aun así hace clic en tu anuncio, esos clics estarán en la tabla «click». Además, si los usuarios hacen clic en un anuncio pero no abren tu app, también habrá clics en esta tabla.
WITH
clicks AS (
SELECT
'2022-03-29 15:03:02 UTC' AS event_time,
'click' AS event_name,
'cpanetwork1' AS media_source,
'aaa111-bbb111' AS advertising_id
UNION ALL
SELECT '2022-03-30 16:15:24 UTC', 'click', 'cpanetwork2', 'aaa111-bbb111'
UNION ALL
SELECT '2022-04-01 17:15:00 UTC', 'click', 'cpanetwork3', 'aaa111-bbb111'
UNION ALL
SELECT '2022-03-31 16:03:47 UTC', 'click', 'mobtop', 'aaa111-bbb111'
UNION ALL
SELECT '2022-03-28 17:15:23 UTC', 'click', 'cpanetwork1', 'ccc555-ddd555'
UNION ALL
SELECT '2022-03-30 18:15:32 UTC', 'click', 'cpanetwork2', 'ccc555-ddd555'
),
SELECT
*
FROM clicks

Installs
- Contiene la fuente de la instalación y las media sources que asistieron. Recuerda: si la aplicación se descarga pero el usuario nunca la abre, AppsFlyer no podrá registrar ninguna instalación.
- Si hubo clics desde la SRN de un usuario concreto pero ninguna instalación suya, no habrá datos.
- Hay datos de varios partners, ya que Facebook casi siempre aparecerá como restricted.
- La ventana de reatribución «es un intervalo de tiempo que empieza la primera vez que un usuario instala la app y dura lo que fije el anunciante (90 días por defecto). Durante la ventana de reatribución, las reinstalaciones de la app no se registran como instalaciones nuevas y no generan un nuevo postback de instalación salvo que procedan de una campaña de retargeting».20

Esto significa que si el usuario desinstala tu app durante la ventana de reatribución y la reinstala desde
- otra campaña de captación de usuarios, no habrá atribución (= no hay evento de instalación) o
- campañas de retargeting (¡marcadas como retargeting!, por ejemplo retargeting_is_true), en cuyo caso se registrará en la tabla
“inapps_retargeting”(no en la tabla“install”) conretargeting_conversion_typeigual a“reattribution”.
También conviene recordar que «con independencia de la ventana de reatribución, cuando una aplicación se respalda con iCloud y después se restaura (en el mismo dispositivo o en otro), AppsFlyer no lo cuenta como una instalación nueva ni como una reinstalación. Un usuario que restaura una app desde iCloud conserva su AppsFlyer ID y sus datos de atribución 21».
Digamos algo sobre cómo asigna AppsFlyer los contribuidores a una instalación. Cuando el usuario abre tu app por primera vez, se inicializa el SDK de AppsFlyer. AppsFlyer consulta entonces sus bases de datos de clics e impresiones de todos los partners integrados que estén activados en tu cuenta de AppsFlyer. AppsFlyer también envía llamadas de API a las SRN. Cuando recibe respuesta, usa el advertising_id o varios parámetros como el user agent + la IP (para la atribución probabilística) para atribuir, teniendo en cuenta estas prioridades:
- Tipo de interacción (un clic vale más que una impresión)
- Método de atribución (el determinista vale más que el probabilístico)
- Ventana de atribución (ventanas de conversión retroactiva por clic o por visualización)

El ganador se asigna como media_source de la instalación y todos los demás como contribuidores.
Ahora bien, si el ganador está en la lista de restringidos = la media_source es restricted, se borran todos los datos sobre los contribuidores (asistencias) a la instalación. Si el contribuidor está en la lista de fuentes de datos restringidas, el valor del campo es null. Es decir, no todos los null en los datos de contribuidores significan que no haya contribuidor.
Unas palabras sobre los métodos de atribución determinista y probabilístico:
-
El método determinista significa que AppsFlyer envía el advertising_id asociado a la instalación a todas las redes integradas activas y, si una de ellas le responde que ese advertising_id es suyo, asigna la instalación a esa red concreta.
-
Hay casos en los que no se encuentran datos de la fuente de instalación mediante ese ID. AppsFlyer dispone de un conjunto de atributos de esa instalación, como device_id, IP, etc., y las redes también pueden compartir esa información. Con los datos de las últimas 24 horas, AppsFlyer compara las coincidencias de cada atributo con los datos de las redes.
Por ejemplo, tenemos los datos de abajo:
Atributos
AppsFlyer
Red-1
Red-2
IP
111.111111.111
222.222
UserAgent
123234
123
plataforma
iOSAndroid
iOS
La Red-1 coincide con los datos de AppsFlyer en 1 de 3 y la Red-2 en 2 de 3. AppsFlyer atribuye la instalación a la Red-2. Es un método probabilístico muy simple, pero AppsFlyer afirma que acierta la media_source de la instalación con un 93 % de probabilidad.
WITH
installs AS (
SELECT
'2022-03-31 18:03:47 UTC' AS install_time,
'install' AS event_name,
'googleads' AS media_source,
'2022-03-31 17:03:47 UTC' AS contributor_1_touch_time,
'twitter' AS contributor_1_media_source,
'2022-03-31 16:03:47 UTC' AS contributor_2_touch_time,
'mobtop' AS contributor_2_media_source,
'2022-03-31 15:03:47 UTC' AS contributor_3_touch_time,
'organic' AS contributor_3_media_source,
'111-222' AS appsflyer_id,
'aaa111-bbb111' AS advertising_id
UNION ALL
SELECT '2022-03-30 18:05:47 UTC', 'install', 'organic', null, null, null, null, null, null, '333-444', 'bbb222-ccc222'
UNION ALL
SELECT '2022-03-29 17:06:50 UTC', 'install', 'facebook', '2022-03-28 15:06:50 UTC', 'mobtop', null, null, null, null, '555-666', 'ccc555-ddd555'
UNION ALL
--must be deleted due to fraudulent install
SELECT '2022-03-29 17:07:50 UTC', 'install', 'fraudcpa', null, null, null, null, null, null, '777-888', 'eee777-fff777'
UNION ALL
--media_source will be changed due to fraudulent install on organic
SELECT '2022-03-29 17:08:50 UTC', 'install', 'fraudcpa', null, null, null, null, null, null, '999-100', 'ggg999-iii999'
UNION ALL
--media_source will be changed due to fraudulent install on contributor1
SELECT '2022-03-29 18:08:50 UTC', 'install', 'fraudcpa', '2022-03-29 15:00:50 UTC', 'goodcpa', null, null, null, null, '100-200', 'jjj100-kkk100'
)
SELECT
*
FROM installs

Post-Attribution Installs
Una de las tablas de Protect360 da la media_source correcta cuando considera fraudulenta la actual.
Recuerda: «Protect360 realiza la detección posterior a la atribución durante el mes natural de la instalación y hasta el séptimo día del mes siguiente. Esto significa, por ejemplo, que para las instalaciones de noviembre Protect360 busca fraude hasta el 7 de diciembre 22».
WITH
post_attribution_installs AS (
SELECT
'777-888' AS appsflyer_id,
NULL AS rejected_reason_value
UNION ALL
SELECT '999-100', 'organic'
UNION ALL
SELECT '100-200', 'contributor1'
)
SELECT
*
FROM post_attribution_installs

In-apps (eventos)
Recuerda: existe una doble atribución de los eventos de retargeting. Significa que los eventos de las campañas de retargeting también se atribuyen a la campaña de instalación de ese appsflyer_id. El resultado es una duplicación de eventos: uno se guarda en la tabla in-apps (eventos) y otro en inapps_retargeting (eventos). La diferencia es que, en la tabla in-apps, el evento procedente de campañas de retargeting lleva el campo is_primary_attribution = false 23. Por eso filtramos los eventos con esos valores.
inapps AS (
SELECT
'2022-03-31 18:02:47 UTC' AS install_time,
'content_view' AS event_name,
'2022-03-31 18:06:02 UTC' AS event_time,
'googleads' AS media_source,
TRUE AS is_primary_attribution,
'111-222' AS appsflyer_id,
null AS customer_user_id
UNION ALL
SELECT '2022-03-31 18:02:47 UTC', 'registation', '2022-03-31 18:09:47 UTC', 'googleads', TRUE, '111-222', '111'
--double_attribution for '111-222'
UNION ALL
SELECT '2022-03-31 18:02:47 UTC', 'login', '2022-04-01 20:07:02 UTC', 'googleads', FALSE, '111-222', '111'
--
UNION ALL
SELECT '2022-03-30 18:05:47 UTC', 'content_view', '2022-03-30 18:07:47 UTC', 'organic', TRUE, '333-444', '333'
UNION ALL
SELECT '2022-03-29 17:06:47 UTC', 'content_view', '2022-03-29 17:08:50 UTC', 'facebook', TRUE, '555-666', null),
SELECT
*
FROM inapps

En esta tabla hemos puesto la hora de instalación del appsflyer_id 111-222 antes que en la tabla de instalaciones para el mismo usuario. ¿Por qué?
El primer motivo posible es este:
- A veces la hora de instalación de la tabla
installses distinta de la de la tablainapps. Por eso tenemos que considerar que hay un evento“install”antes que todos los demás eventos. Es perfectamente normal. AppsFlyer lo hace por el comportamiento anómalo de la instalación.
Otros motivos posibles:
- A veces puede haber varias fuentes de instalación para un mismo
appsflyer_id(por el mismo motivo del punto 1 de arriba). - A veces habrá más de un appsflyer_id para un mismo user_id (
customer_user_id), porque ese ID de usuario instaló tu app dos veces. ¡Cada instalación inicializa unappsflyer_idnuevo! De nuevo, hay que recordar que una zona no autenticada pequeña es imprescindible para tener datos completos del usuario. - A veces habrá más de un user_id (
customer_user_id) para un mismoappsflyer_id, porque el usuario se ha registrado varias veces.
Clicks_Retargeting
Son clics en anuncios de retargeting pero sin las SRN, como Google, Facebook, etc. Recuerda: si un usuario ya ha vuelto a interactuar con tu app pero aun así hace clic en tu anuncio de retargeting, esos clics estarán en la tabla «click_retargeting». Además, si los usuarios hacen clic en un anuncio de retargeting pero no abren tu app, también habrá clics en esta tabla.
WITH
clicks_retargeting AS (
SELECT
'2022-04-01 21:03:02 UTC' AS event_time,
'click' AS event_name,
'email' AS media_source,
'aaa111-bbb111' AS advertising_id
UNION ALL
SELECT '2022-03-31 08:15:23 UTC', 'click', 'google_retarg', 'bbb222-ccc222'
)
SELECT
*
FROM clicks_retargeting

In-apps_Retargeting (eventos)
- Esta tabla guarda los datos de eventos de las campañas de retargeting, incluidas las SRN. El evento más importante es el de apertura de la app. (En nuestro caso es
“content_view”.) La campaña de retargeting se atribuye a ese evento. Imagina que a AppsFlyer solo se le envía“purchase”. Es poco probable que ese evento ocurra con frecuencia, comparado con otras interacciones posibles dentro de tu app, y si el usuario hace clic en tu anuncio de retargeting, llega a tu app y no compra nada, no habrá datos en esta tabla sobre ese contacto. - También puede haber estos eventos de atribución: - Re-engagement: si la app está en el dispositivo del usuario, la media source que le motivó a abrirla recibe el mérito de un re-engagement. - Reatribución: reinstalación por retargeting. Lo repetimos: si el usuario desinstala tu app durante la ventana de reatribución y la reinstala a través de campañas de retargeting, todos los eventos in-app de ese usuario se registrarán con
retargeting_conversion_typeigual a“reattribution”. Así que puedes convertir en «reinstall» el primer evento de la campaña de retargeting cuandoretargeting_conversion_type=“reattribution”. - Digamos también algo del caso en que hay dos campañas de retargeting para el mismo usuario. Si el usuario interactúa con otra campaña de retargeting durante la ventana de re-engagement de la anterior, y no lo bloquea una ventana de exclusión, la ventana de re-engagement actual (la de la campaña anterior) termina de inmediato y empieza una nueva. En inapps_retargeting quedará la última campaña (la segunda) tras interactuar con ella y abrir la aplicación, así que a partir de ahí los eventos irán a la segunda campaña hasta que acabe la nueva ventana de re-engagement.
- Recordemos también que existe una doble atribución de los eventos de retargeting. Significa que los eventos de las campañas de retargeting también se atribuyen a la campaña de instalación de ese appsflyer_id. El resultado es una duplicación de eventos: uno se guarda en la tabla in-apps (eventos) y otro en inapps_retargeting (eventos). La diferencia es que, en la tabla in-apps_retargeting, el evento procedente de campañas de retargeting lleva el campo
is_primary_attribution = true.
WITH
inapps_retargeting AS (
SELECT
'email' AS media_source,
'2022-04-01 19:06:02 UTC' AS event_time,
'content_view' AS event_name,
'111-222' AS appsflyer_id,
'111' AS customer_user_id,
NULL AS retargeting_conversion_type
UNION ALL
SELECT 'email', '2022-04-01 20:07:02 UTC', 'login', '111-222', '111', NULL
UNION ALL
SELECT 'google_retarg', '2022-03-31 09:07:00 UTC', 'content_view', '333-444', '333', 'reattribution'
UNION ALL
SELECT 'google_retarg', '2022-03-31 09:07:02 UTC', 'registration', '333-444', '333', NULL
UNION ALL
SELECT 'cpanetwork_retarg', '2022-03-31 10:10:10 UTC', 'content_view', '100-200', NULL, NULL
UNION ALL
SELECT 'cpanetwork_retarg', '2022-03-31 13:15:10 UTC', 'content_view', '999-100', NULL, NULL
),
SELECT
*
FROM inapps_retargeting

Post-Attribution In-app Events
WITH
post_attribution_in_app_events AS (
SELECT
'2022-03-31 10:10:10 UTC' AS event_time,
'content_view' AS event_name,
'100-200' AS appsflyer_id,
TRUE AS is_retargeting,
'organic' AS rejected_reason_value
UNION ALL
SELECT '2022-03-31 13:15:10 UTC', 'content_view', '999-100', TRUE, NULL
)
SELECT
*
FROM post_attribution_in_app_events

Organic_Uninstalls
Ten en cuenta que la hora del evento representa el momento en que AppsFlyer determina que se desinstaló la app, y no la desinstalación en sí 24.
WITH
organic_uninstalls AS (
SELECT
'googleads' AS media_source,
'2022-04-01 10:09:47 UTC' AS event_time,
'333-444' AS appsflyer_id
)
SELECT
*
FROM organic_uninstalls

Uninstalls
Ten en cuenta que la hora del evento representa el momento en que AppsFlyer determina que se desinstaló la app, y no la desinstalación en sí.
WITH
uninstalls AS (
SELECT
'googleads' AS media_source,
'2022-04-02 18:09:47 UTC' AS event_time,
'111-222' AS appsflyer_id
)
SELECT
*
FROM uninstalls

Raw_Data
Ahora toca combinar los datos uniéndolos sin más y aplicando algunas transformaciones sencillas.
raw_data AS
(SELECT
CASE
WHEN true_install_time_by_userid IS NULL THEN true_install_time_by_afid
ELSE true_install_time_by_afid
END AS true_install_time,
*
FROM
(SELECT
FIRST_VALUE(install_time) OVER (PARTITION BY customer_user_id ORDER BY install_time ASC) AS true_install_time_by_userid,
FIRST_VALUE(install_time) OVER (PARTITION BY appsflyer_id ORDER BY install_time ASC) AS true_install_time_by_afid,
*
FROM
(SELECT
LAST_VALUE(customer_user_id IGNORE NULLS) OVER (PARTITION BY appsflyer_id ORDER BY install_time DESC RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS customer_user_id,
appsflyer_id,
media_source,
true_install_source,
clicks_media_source,
clicks_event_name,
clicks_event_time,
install_time,
contributor_1_media_source,
contributor_1_touch_time,
contributor_2_media_source,
contributor_2_touch_time,
contributor_3_media_source,
contributor_3_touch_time,
clicks_retarg_source,
clicks_retarg_event_name,
clicks_retarg_event_time,
retarg_source,
retarg_event_name,
retarg_event_time,
inapps_media_source,
inapps_event_name,
inapps_event_time,
uninstall_organic_source,
uninstall_organic_time,
uninstall_nonorg_source,
uninstall_nonorg_time
FROM
(SELECT
DISTINCT
customer_user_id,
--delete fraud installs
CASE
WHEN problem_install_afid IS NOT NULL AND true_install_source IS NULL THEN 'delete'
ELSE appsflyer_id
END AS appsflyer_id,
CASE
WHEN problem_install_afid IS NOT NULL AND true_install_source = 'organic' THEN 'organic'
WHEN problem_install_afid IS NOT NULL AND true_install_source = 'contributor1' THEN contributor_1_media_source
WHEN problem_install_afid IS NOT NULL AND true_install_source = 'contributor2' THEN contributor_2_media_source
WHEN problem_install_afid IS NOT NULL AND true_install_source = 'contributor3' THEN contributor_3_media_source
ELSE media_source
END AS media_source,
true_install_source,
clicks_media_source,
clicks_event_name,
clicks_event_time,
CASE
WHEN install_time_inapp IS NULL THEN install_time_installs
ELSE install_time_inapp
END AS install_time,
CASE
WHEN problem_install_afid IS NOT NULL AND true_install_source = 'contributor1' THEN NULL
ELSE contributor_1_media_source
END AS contributor_1_media_source,
CASE
WHEN problem_install_afid IS NOT NULL AND true_install_source = 'contributor1' THEN NULL
ELSE contributor_1_touch_time
END AS contributor_1_touch_time,
CASE
WHEN problem_install_afid IS NOT NULL AND true_install_source = 'contributor2' THEN NULL
ELSE contributor_2_media_source
END AS contributor_2_media_source,
CASE
WHEN problem_install_afid IS NOT NULL AND true_install_source = 'contributor2' THEN NULL
ELSE contributor_2_touch_time
END AS contributor_2_touch_time,
CASE
WHEN problem_install_afid IS NOT NULL AND true_install_source = 'contributor3' THEN NULL
ELSE contributor_3_media_source
END AS contributor_3_media_source,
CASE
WHEN problem_install_afid IS NOT NULL AND true_install_source = 'contributor3' THEN NULL
ELSE contributor_3_touch_time
END AS contributor_3_touch_time,
clicks_retarg_source,
clicks_retarg_event_name,
clicks_retarg_event_time,
--make nulls in retargeting columns if fraud
CASE
WHEN true_retarg_source = 'delete' THEN NULL
WHEN true_retarg_source IS NOT NULL AND true_retarg_source != 'delete' THEN true_retarg_source
ELSE retarg_source
END AS retarg_source,
CASE
WHEN true_retarg_source = 'delete' THEN NULL
ELSE retarg_event_name
END AS retarg_event_name,
CASE
WHEN true_retarg_source = 'delete' THEN NULL
ELSE retarg_event_time
END AS retarg_event_time,
inapps_media_source,
inapps_event_name,
inapps_event_time,
uninstall_organic_source,
uninstall_organic_time,
uninstall_nonorg_source,
uninstall_nonorg_time
FROM
(SELECT
inapps.customer_user_id,
installs.appsflyer_id,
installs.media_source,
post_attribution_installs.appsflyer_id AS problem_install_afid,
rejected_reason_value AS true_install_source,
clicks.media_source AS clicks_media_source,
clicks.event_name AS clicks_event_name,
CAST(clicks.event_time AS STRING) AS clicks_event_time,
CAST(installs.install_time AS STRING) AS install_time_installs,
CAST(inapps.install_time AS STRING) AS install_time_inapp,
installs.contributor_1_media_source,
CAST(installs.contributor_1_touch_time AS STRING) AS contributor_1_touch_time,
installs.contributor_2_media_source,
CAST(installs.contributor_2_touch_time AS STRING) AS contributor_2_touch_time,
installs.contributor_3_media_source,
CAST(installs.contributor_3_touch_time AS STRING) AS contributor_3_touch_time,
clicks_retarg.media_source AS clicks_retarg_source,
clicks_retarg.event_name AS clicks_retarg_event_name,
CAST(clicks_retarg.event_time AS STRING) AS clicks_retarg_event_time,
retarg.media_source AS retarg_source,
retarg.event_name AS retarg_event_name,
CAST(retarg.event_time AS STRING) AS retarg_event_time,
fraud_retarg.media_source AS true_retarg_source,
inapps.media_source AS inapps_media_source,
inapps.event_name AS inapps_event_name,
CAST(inapps.event_time AS STRING) AS inapps_event_time,
organic_uninstalls.media_source AS uninstall_organic_source,
CAST(organic_uninstalls.event_time AS STRING) AS uninstall_organic_time,
uninstalls.media_source AS uninstall_nonorg_source,
CAST(uninstalls.event_time AS STRING) AS uninstall_nonorg_time
FROM
(SELECT
appsflyer_id,
advertising_id,
media_source,
install_time,
contributor_1_media_source,
contributor_1_touch_time,
contributor_2_media_source,
contributor_2_touch_time,
contributor_3_media_source,
contributor_3_touch_time
FROM installs
) AS installs
LEFT JOIN
(SELECT
advertising_id,
media_source,
event_name,
event_time
FROM clicks
) AS clicks
ON installs.advertising_id = clicks.advertising_id
LEFT JOIN
(SELECT
appsflyer_id,
rejected_reason_value
FROM post_attribution_installs
) AS post_attribution_installs
ON installs.appsflyer_id = post_attribution_installs.appsflyer_id
LEFT JOIN --add customer_user_id, because all customer_user_id are NULL at installs
(SELECT
FIRST_VALUE (customer_user_id IGNORE NULLS) OVER (PARTITION BY appsflyer_id ORDER BY install_time) AS customer_user_id,
appsflyer_id,
media_source,
install_time,
event_name,
event_time
FROM inapps
WHERE is_primary_attribution = TRUE
) AS inapps
ON installs.appsflyer_id = inapps.appsflyer_id
LEFT JOIN
(SELECT
advertising_id,
media_source,
event_name,
event_time
FROM clicks_retargeting
) AS clicks_retarg
ON installs.advertising_id = clicks_retarg.advertising_id
LEFT JOIN --add retarteting campaigns and events name with event_time (as contidition)
(SELECT
customer_user_id,
appsflyer_id,
CONCAT('utm_retargeting=', media_source) AS media_source,
event_time,
CASE
WHEN retargeting_conversion_type = 'reattribution' THEN 'reinstall'
ELSE event_name
END AS event_name
FROM inapps_retargeting
) AS retarg
ON installs.appsflyer_id = retarg.appsflyer_id
LEFT JOIN
(SELECT
appsflyer_id,
CASE
WHEN rejected_reason_value IS NULL THEN 'delete'
ELSE CONCAT('utm_retargeting=', rejected_reason_value)
END AS media_source,
event_time,
event_name
FROM post_attribution_in_app_events
WHERE is_retargeting IS TRUE
) AS fraud_retarg
ON retarg.appsflyer_id = fraud_retarg.appsflyer_id
AND retarg.event_time = fraud_retarg.event_time
AND retarg.event_name = fraud_retarg.event_name
LEFT JOIN
(SELECT
appsflyer_id,
IF(media_source IS NULL, 'none', 'none') AS media_source,
event_time
FROM organic_uninstalls
) AS organic_uninstalls
ON installs.appsflyer_id = organic_uninstalls.appsflyer_id
LEFT JOIN
(SELECT
appsflyer_id,
IF(media_source IS NULL, 'none', 'none') AS media_source,
event_time
FROM uninstalls
) AS uninstalls
ON installs.appsflyer_id = uninstalls.appsflyer_id
)
)
)
--excluding user with adif = '777-888' AND
WHERE (appsflyer_id != 'delete')
)
)
SELECT
*
FROM raw_data

(Con contributor_2 y el resto de columnas)
Channels
Ahora necesitamos guardar los canales en una sola columna. Para eso hay que usar la función UNPIVOT.
channels AS
(SELECT
DISTINCT
appsflyer_id,
customer_user_id,
CASE
WHEN channel LIKE '%utm_retargeting%' THEN REGEXP_EXTRACT(channel, 'utm_retargeting=([^&]*)')
ELSE channel
END AS channel,
CASE
WHEN channel LIKE '%utm_retargeting%' THEN 1
ELSE 0
END AS is_retarg,
true_install_time,
CAST(touch_time AS TIMESTAMP) AS touch_time
FROM
raw_data
UNPIVOT ((channel, touch_time) FOR channel_time_pair IN
((clicks_media_source, clicks_event_time),
(media_source, install_time),
(contributor_1_media_source, contributor_1_touch_time),
(contributor_2_media_source, contributor_2_touch_time),
(contributor_3_media_source, contributor_3_touch_time),
(clicks_retarg_source, clicks_retarg_event_time),
(inapps_media_source, inapps_event_time),
(retarg_source, retarg_event_time),
(uninstall_organic_source, uninstall_organic_time),
(uninstall_nonorg_source, uninstall_nonorg_time)
))
)
SELECT
*
FROM channels

Events
Hay que hacer lo mismo con los eventos. No podemos usar UNPIVOT a la vez para canales y eventos porque obtendrías un CROSS JOIN de eventos con canales, que generaría filas sin sentido.
events AS
(SELECT
DISTINCT
appsflyer_id,
customer_user_id,
event,
CAST(time_of_event AS TIMESTAMP) AS time_of_event
FROM
raw_data
UNPIVOT ((event, time_of_event) FOR event_time_pair IN
((clicks_event_name, clicks_event_time),
(media_source, install_time),
(clicks_retarg_event_name, clicks_retarg_event_time),
(inapps_event_name, inapps_event_time),
(retarg_event_name, retarg_event_time)
))
)
SELECT
*
FROM events

Recorrido del usuario
El siguiente paso es unirlo todo, creando un “evento” de instalación a partir de la marca de tiempo y convirtiendo algunos canales en “direct/none”, como proponíamos antes.
user_journey AS
(SELECT
appsflyer_id,
customer_user_id,
CASE
WHEN LAG(channel) OVER (PARTITION BY appsflyer_id, customer_user_id, is_retarg ORDER BY touch_time ASC ) = channel
AND event_name = 'install' THEN channel
WHEN LAG(channel) OVER (PARTITION BY appsflyer_id, customer_user_id, is_retarg ORDER BY touch_time ASC ) = channel
AND event_name IS NOT NULL THEN 'direct/none'
ELSE channel
END AS channel,
touch_time,
--or leave it as null if you wish
--event_name
CASE WHEN
event_name IS NULL THEN 'click'
ELSE event_name
END AS event_name
FROM(
SELECT
appsflyer_id,
customer_user_id,
channel,
is_retarg,
touch_time,
CASE
--where can not be events before install!
WHEN CAST(true_install_time AS TIMESTAMP) > CAST(touch_time AS TIMESTAMP) THEN NULL
--install event
WHEN CAST(true_install_time AS TIMESTAMP) = touch_time THEN 'install'
WHEN channel = 'none' THEN 'uninstall'
WHEN event = 'click' THEN NULL
ELSE event
END AS event_name
FROM
(SELECT
channels.appsflyer_id,
channels.customer_user_id,
channel,
is_retarg,
true_install_time,
touch_time,
event
FROM
(SELECT
*
FROM channels) AS channels
LEFT JOIN
(SELECT
*
FROM events) AS events
ON channels.appsflyer_id = events.appsflyer_id
AND channels.customer_user_id = events.customer_user_id
AND channels.touch_time = events.time_of_event
ORDER BY channels.appsflyer_id, touch_time)
)
)
SELECT
*
FROM user_journey
ORDER BY appsflyer_id, customer_user_id, touch_time ASC

Caminos de conversión
Ahora convertimos “install” en la conversión (puedes cambiarlo por el registro, por ejemplo):
conversion_paths_by_user AS
(SELECT
appsflyer_id,
customer_user_id,
channel,
event_name
FROM
(SELECT
*,
--MIN = first interaction with desired event_name
MIN(CASE WHEN event_name = 'install' THEN touch_number END) OVER (PARTITION BY appsflyer_id, customer_user_id) AS conversion
FROM
(SELECT
*,
ROW_NUMBER() OVER (PARTITION BY appsflyer_id, customer_user_id) AS touch_number
FROM user_journey)
)
WHERE touch_number <= conversion
)
SELECT
*
FROM conversion_paths_by_user

Tiene buena pinta, pero como decíamos antes, ¡puede haber canales adicionales que no aparezcan aquí!
Embudo multicanal
Este es nuestro resultado final:
multi_channel_funnel AS (
SELECT
multi_channel_funnel,
COUNT(multi_channel_funnel) AS count
FROM
(SELECT
ARRAY_TO_STRING(multi_channel_funnel, '->') AS multi_channel_funnel
FROM
(SELECT
appsflyer_id,
customer_user_id,
ARRAY_AGG(channel) AS multi_channel_funnel
FROM conversion_paths_by_user
GROUP BY 1, 2)
)
GROUP BY 1)
SELECT
*
FROM multi_channel_funnel

Consultas
Aquí tienes la consulta con datos de ejemplo, que puedes reescribir fácilmente para tus datos reales. Cambia el nombre de las tablas si hace falta y elige el intervalo de fechas. Eso es todo. Esperamos que te resulte útil. Escríbenos si quieres una consultoría.
1 Que se pueden combinar en uno solo, por supuesto.
2 Nota: usando dominios o cupones únicos personalizados también puedes medir algunos anuncios offline.
4 Usaremos NULL como sinónimo.
5 Esto también significa que no puedes comparar los datos en bruto de clics e impresiones con los datos de la cuenta publicitaria de la SRN: solo puedes hacerlo con informes de AppsFlyer como Overview, dentro de su interfaz.
6 Lo mismo ocurre con la tabla de impresiones. No la usaremos porque los datos de impresiones también están restringidos para la web.
7 https://support.appsflyer.com/hc/en-us/articles/360006868017-Raw-data-content-restriction-media-source-or-user-identifiers – Facebook, TikTok, Twitter, Roku.
9 Nota: los clics y las impresiones también son engagements.
10 Si envías un evento de servidor a servidor, acuérdate de añadir el parámetro appsflyer_id para que la atribución sea correcta.
11 Se te cobra cuando el usuario abre la app tras hacer clic en la campaña de retargeting. Las aperturas siguientes no se facturan.
12 ¡Solo para campañas de instalación!
13 No hay otro identificador para hacerlo.
15 Si falta alguna tabla, tendrás que mirar tus flujos de datos de Data Locker.
16 https://support.appsflyer.com/hc/en-us/articles/207040506-Assisted-Installs#assisted-installs.
17 Es difícil saber por qué AppsFlyer lo llama así, pero deberías tratarlo como una “true_media_source”.
18 https://support.appsflyer.com/hc/en-us/articles/208387843#data-fields-dictionary
19 ¿Te acuerdas de cómo comprobar las discrepancias entre los clics de Google Ads y las sesiones web? Es fácil, porque tienes un referrer en los datos en bruto de Google Analytics. Con las apps no puedes hacerlo, y no solo porque las tiendas no compartan información contigo: ¡con AppsFlyer pasa lo mismo! Por desgracia, solo queda tener fe.
20 https://support.appsflyer.com/hc/en-us/articles/115002587066-Re-attribution-window-explained
24 https://support.appsflyer.com/hc/en-us/articles/4408933557137#overview%C2%A0
Traducido del inglés con Claude.
← Volver a Last Click City