[BETA] Migrar de PBA a la medición de performance web

En resumen: La Atribución People-Based (PBA) está siendo sustituida por la medición de performance web, la solución mejorada de AppsFlyer para la medición web. Este artículo explica qué cambia, los pasos de la migración y el mapeo completo de campos para tus reportes e integraciones del lado del servidor.

Por qué estamos mejorando tu medición web

La medición web es más importante que nunca. El sitio web es donde muchos de tus usuarios convierten y donde comienza el recorrido hacia tu aplicación móvil. No es solo una landing page; es un funnel de adquisición completo que incluye flujos de cuestionarios, onboarding personalizado, paywalls y tiendas web que suelen atraer a usuarios con mayor intención a un costo de adquisición menor.

AppsFlyer está llevando la medición web al mismo nivel que la medición móvil. La Atribución People-Based (PBA), el producto web heredado, está siendo sustituido por la medición de performance web, desarrollada sobre el motor de atribución principal y el modelo de datos unificado de AppsFlyer. La medición de performance web ofrece todas las funcionalidades actuales de PBA y, además, incorpora:

  • Una single source of truth, con la medición de la web y la aplicación móvil en un solo lugar, junto con la agregación de costos, los postbacks de optimización y la optimización de creatividades.
  • Mejores decisiones presupuestarias, con una atribución flexible que se adapta a la lógica de tu negocio para que puedas aumentar la inversión en las campañas que realmente funcionan.
  • ROAS mejorado, con postbacks de optimización enriquecidos para tus ad networks y una configuración completa del lado del servidor que mide más conversiones, para que las redes puedan optimizar utilizando mejores señales.
  • ROI multiplataforma real, mide cada recorrido del usuario en la web, la aplicación y cualquier otra plataforma.
  • Reportes sencillos y unificados, un único dashboard y un único conjunto de reportes de datos para todas las plataformas, actualizados cada hora.

Nota

El cambio requiere un esfuerzo mínimo por tu parte. Mantendrás el mismo SDK web, sin necesidad de realizar cambios en el código de tu sitio web.

¡Importante!

Ten en cuenta que habrá algunas diferencias en las cifras. La lógica de atribución de la medición de performance web es más avanzada y flexible, por lo que las cifras no coincidirán exactamente con las de PBA.

Medición de performance web vs PBA

Capacidad Descripción PBA Medición de performance web
Datos
Actualización de los datos Rapidez con la que los datos están disponibles para los reportes ✗ A diario ✓ Reportes por hora, con aproximadamente 2 horas de retraso en la actualización de los datos
Modelo de datos Alineación del esquema con los datos de atribución móvil ✗ Diferente al de dispositivos móviles ✓ Alineado con los datos móviles, fácil de combinar y analizar
Reportes
Dashboard de actividad Dashboard basado en la hora del evento ✓ Compatible ✓ Compatible
Dashboard de cohorte Analiza el performance por cohorte de adquisición a lo largo del tiempo ✗ No compatible ✓ Compatible
Raw data de Data Locker Acceso a raw data de atribución mediante la exportación de Data Locker ✓ Compatible ✓ Compatible
Granularidad a nivel de anuncio Desglose de los reportes desde el nivel de campaña hasta el nivel de anuncio ✗ Solo en el dashboard, no en los raw data ✓ Granularidad completa de las campañas tanto en el dashboard como en los raw data
LTV multiplataforma Unifica los recorridos de usuarios de la web y los dispositivos móviles en un único LTV ✗ No compatible ✓ Compatible
Costos y señales
Costos web Ingiere y genera reportes sobre la inversión publicitaria de las campañas web ✗ No compatible ✓ Compatible
Postbacks de optimización Envía señales de optimización a las ad networks ✗ No compatible ✓ Compatible
Implementación
SDK Web (píxel) SDK del lado del cliente para medir visitas y eventos web ✓ Compatible ✓ El mismo SDK, sin necesidad de realizar cambios en el código
Compatibilidad con S2S Medición completa del lado del servidor para visitas y eventos ✗ Solo eventos ✓ Eventos y visitas Admite una implementación completa del lado del servidor.
Motor de atribución
Ventanas de atribución Ventanas de lookback, de atribución, de re-engagement y de inactividad configurables ✗ Solo ventanas de lookback de clics ✓ 5 ventanas configurables (control total)
Evento de UA personalizado Define qué evento se considera una adquisición de usuario más allá de la primera visita ✗ Solo la primera visita ✓ Cualquier evento personalizado (por ejemplo, registro o compra)
UA vs retargeting Crédito de atribución para nuevas adquisiciones vs re-engagement ✗ Sin concepto de UA; los eventos se atribuyen al último toque ✓ Vistas específicas de UA y retargeting con doble atribución

Pasos de la migración

La siguiente tabla divide la migración en cinco pasos. La creación de una aplicación web es necesaria para todos; el resto de los pasos dependen de las funcionalidades de PBA que utilices actualmente, como la API S2S o los reportes de Data Locker.

Acción Relevante para En qué consiste
Crear una aplicación web Todos Cuando crees la nueva aplicación web, selecciona tu Dev Key web actual en el campo ID del SDK web. De este modo, el código actual de tu sitio web seguirá funcionando tal cual, sin necesidad de realizar cambios. A continuación, configura los ajustes de atribución en la nueva aplicación.
Migrar a la nueva API S2S Usuarios de la API S2S de PBA Configura la nueva API S2S. Admite tanto visitas como eventos, por lo que puedes realizar una implementación completamente del lado del servidor. Consulta el apéndice sobre la migración de la API S2S que aparece a continuación.
Migrar a los nuevos reportes de Data Locker Usuarios de los reportes de Data Locker de PBA Habilita el nuevo reporte web en la interfaz de usuario y configura tu BI/ETL para que utilice los datos actualizados. Los nuevos reportes utilizan un único esquema compartido entre todas las plataformas: aplicaciones móviles, sitios web, CTV y PC. Consulta el apéndice sobre el mapeo de campos de raw data que aparece a continuación.
Revisar los parámetros de atribución Recomendado PBA aplicaba sus propias reglas de atribución de fuente de medios. La medición de performance web utiliza una resolución mejorada de las fuentes de tráfico para ofrecer análisis más precisos. Revisa las diferencias para saber qué valores puedes esperar en tus reportes. Consulta el apéndice de comparación de la resolución de fuentes de tráfico que aparece a continuación.
Agrupar aplicaciones en una línea de productos Opcional Crea una línea de productos y agrupa la aplicación web con tus aplicaciones móviles para habilitar los reportes de LTV de usuarios multiplataforma (similares a PBA Brand Bundle).

Nota

Ten en cuenta que habrá algunas diferencias en las cifras. La lógica de atribución de la medición de performance web es más avanzada y flexible, por lo que las cifras no coincidirán exactamente con las de PBA. La lógica de registro de sesiones no cambia; una sesión sigue teniendo una duración de 30 minutos de actividad, el estándar del mercado.

Funcionalidades que dejarán de estar disponibles

Capacidad Qué cambia Detalles
Ruta de conversión Ya obsoleta.
Instalaciones asistidas por la web (atribución a una campaña web que contribuyó a generar posteriormente una instalación móvil) No hay una vista específica de “instalaciones asistidas por la web”.

Los recorridos web-to-app se miden con Smart Script y Smart Banner. El reporte multiplataforma proporciona un análisis similar, aunque no idéntico. Al igual que PBA, se basa en CUID:

  • Las campañas web que contribuyeron a una instalación que anteriormente se registraba como orgánica ahora aparecen como adquisición de usuarios no orgánica. Ya no se consideran asistencias; la campaña web es la fuente de adquisición.
  • En las campañas web que contribuyeron a una instalación no orgánica, la campaña web pasa a ser la adquisición de usuario y la instalación móvil pasa a ser retargeting multiplataforma.
Recálculo retroactivo No se realiza ningún recálculo retroactivo más allá del retraso predeterminado de 30 minutos. La atribución se finaliza tras un retraso de 30 minutos, lo que da tiempo a los usuarios para identificarse dentro de esa ventana.
Campos de raw data Los campos relacionados con dispositivos móviles quedan obsoletos para la web. Se cambia el nombre de algunos campos y se modifican algunos valores. Consulta el apéndice sobre el mapeo de campos de raw data que aparece a continuación.
Eventos S2S recibidos con retraso Los eventos que llegan más de 30 minutos después de que finalice el día UTC en el que se produjeron se siguen aceptando, pero la hora del evento se sustituye por la hora en la que AppsFlyer los recibió. Envía los eventos en tiempo real, preferiblemente en los 30 minutos posteriores a que se produzcan, en lugar de agruparlos en una única carga diaria. Una atribución precisa depende de que los eventos se reciban poco después de que se produzcan. Esto alinea el comportamiento de la web con el comportamiento actual de los dispositivos móviles.

Apéndice: cambios en los reportes de raw data

Mapeo único de todos los campos del reporte de raw data de PBA (visitas y eventos del sitio web) al nuevo reporte de eventos del usuario final. Fuente: Reportes de raw data de PBA.

Alcance: los reportes de visitas al sitio web y eventos del sitio web de PBA.

  • Sin cambios, mismo nombre de campo y mismos datos.
  • Renombrado, mismos datos, nuevo nombre de columna.
  • Redefinido, nombre igual o similar, pero cambian los datos o el formato de los valores (debe revisarse antes de volver a utilizarlo).
  • Obsoleto, sin equivalente en el nuevo reporte.
Nombre del campo Descripción Estado Nuevo campo/nota
advertising_id ID de publicidad (GAID) Obsoleto ID de publicidad del dispositivo móvil, unificado entre plataformas en PBA. Los reportes de LTV multiplataforma y recorrido de usuarios se basan en CUID y no utilizan este campo.
af_web_id ID de cookie enviado desde el SDK web Renombrado appsflyer_id_value, misma cookie web.
amazon_aid ID de publicidad de Amazon Fire TV Obsoleto ID de dispositivo móvil, no relevante para la web.
android_id ID de dispositivo de Android Obsoleto ID de dispositivo móvil, no relevante para la web.
app_id ID de la aplicación instalada más recientemente Obsoleto Campo multiplataforma para dispositivos móviles. El identificador de la aplicación web en el nuevo reporte es unified_app_id ("website-{domain}"), un concepto diferente.
app_name Nombre de la aplicación más reciente Sin cambios app_name.
app_version Versión más reciente de la aplicación Obsoleto Campo multiplataforma para dispositivos móviles, no relevante para la web.
appsflyer_id ID de AppsFlyer (instalación) Obsoleto ID de instalación móvil. El nuevo appsflyer_id_value es la cookie web (af_web_id), un identificador diferente.
attributed_touch_time Marca de tiempo de la visita web (atribuida) Renombrado event_time__attribution, la hora del toque atribuido (engagement).
attributed_touch_type Tipo de toque, siempre "web visit" Obsoleto Valor constante en PBA. La distinción entre visita y evento ahora se encuentra en end_user_event_type (SESSION / IN_APP).
bundle_id ID de paquete de PBA Obsoleto Sustituido por la agrupación Product Line (web + aplicaciones móviles) en el reporte multiplataforma.
campaign Atribuido a la visita al sitio web Renombrado campaign_name.
campaign_id Atribuido a la visita al sitio web Sin cambios campaign_id.
city Resuelto utilizando la dirección IP Sin cambios city.
country_code Resuelto utilizando la dirección IP Sin cambios country_code.
customer_user_id Identificador de usuario del cliente (CUID) Sin cambios customer_user_id.
device_type El tipo de dispositivo Renombrado device_category, los valores son diferentes ("Desktop" frente a "MOBILE_PHONE" / "TV").
dma Resuelto utilizando la dirección IP Sin cambios dma.
event_name Visitas: siempre "website visit". Eventos: nombre del evento enviado Redefinido Vacío en las visitas (PBA mostraba "website visit"). Sin cambios para los eventos.
event_revenue Importe de ingresos en la divisa del evento Renombrado revenue_value_original.
event_revenue_currency Código de moneda de 3 dígitos de event_revenue Renombrado revenue_currency_original.
event_revenue_usd event_revenue convertido a USD Renombrado revenue_usd.
event_source El SDK web o Server-to-server Sin cambios event_source.
event_time Visitas: hora de la visita. Eventos: hora del evento Sin cambios event_time.
event_type Evento estándar/evento de conversión/visita al sitio web Redefinido La identificación de eventos de conversión deja de existir. La distinción entre visita y evento se encuentra en end_user_event_type (SESSION / IN_APP).
event_url URL de la página web donde se produjo el evento (equivale a Original URL en las visitas) Sin cambios event_url, ahora también es el lugar principal donde consultar los parámetros de consulta de la URL (UTM, etc.).
event_value Eventos: detalles del evento en formato JSON. Visitas: null Sin cambios event_value.
idfa Identificador de publicidad Obsoleto ID de dispositivo móvil, no relevante para la web.
idfv Identificador de publicidad Obsoleto ID de dispositivo móvil, no relevante para la web.
imei Identificador de dispositivo Obsoleto ID de dispositivo móvil, no relevante para la web.
install_time Hora de la instalación más reciente de la aplicación Obsoleto Campo multiplataforma para dispositivos móviles. La hora de adquisición de usuarios (conversión) web es event_time__conversion, un concepto diferente.
ip Dirección IP del visitante Renombrado ip_address_value, mismo valor, además de ip_address_type para el método de hashing.
language Indicado por el agente de usuario (por ejemplo, "English") Redefinido language, el formato del valor cambia a ISO 639-1 y al código de país.
media_channel Atribuido a la visita al sitio web ("Ad") Obsoleto Se puede obtener mediante sub_param_1-5 si es necesario.
media_source Atribuido a la visita al sitio web Sin cambios media_source.
media_type Atribuido a la visita al sitio web Obsoleto Vacío para la web. La distinción entre orgánico y de pago ahora viene determinada por el valor booleano is_organic.
oaid Identificador de publicidad Obsoleto ID de dispositivo móvil, no relevante para la web.
original_url URL que redirigió al usuario en la visita atribuida Renombrado Consolidado en event_url (en las visitas, event_url equivale a la URL original). La columna independiente deja de existir.
platform La plataforma ("macOS", "Windows") Redefinido La plataforma siempre es "WEBSITE". El sistema operativo se ha trasladado a os_version / user_agent.
postal_code Resuelto utilizando la dirección IP Sin cambios postal_code.
query_params Parámetros de consulta de la URL de redirección, en formato JSON Obsoleto La cadena de raw query se encuentra en event_url; la columna de JSON procesado deja de existir.
referrer HTTP referrer de la visita atribuida al sitio web Renombrado http_referrer.
region Determinado mediante la dirección IP ("NA") Renombrado continent, código de continente, por ejemplo, "NA".
state Resuelto utilizando la dirección IP Sin cambios state.

Prompt listo para usar para un agente de programación para la migración de ETL de Data Locker

Para facilitar esta transición, hemos preparado un prompt listo para usar para tu agente de programación con IA (Claude Code, Cursor o similar). Da al agente acceso a tu código ETL actual y pega el siguiente prompt; contiene el mapeo completo de campos y todas las diferencias de comportamiento. El prompt guiará al agente para que comprenda tu pipeline actual y lo vuelva a crear para el nuevo reporte.

You are migrating an ETL pipeline from AppsFlyer's legacy PBA Data Locker raw-data reports (Website visits + Website events) to AppsFlyer's Web Performance Measurement Data Locker report (End User Events). Everything you need is in this prompt: the structural changes, the complete field-by-field mapping, and the validation steps. Do not guess anything beyond what is written here; if something is ambiguous, ask me.

## Step 1: Learn the current pipeline

Before writing any code, explore the existing codebase and produce an inventory:
1. Which PBA reports we consume (Website visits, Website events, or both) and where they are ingested.
2. Every PBA field we read, and where each is used downstream (transforms, joins, dashboards, alerts, exports).
3. The load cadence and scheduling assumptions (PBA delivered daily).
4. Any logic that separates or joins visit rows and event rows.
5. Any filters, groupings, or hardcoded values keyed on PBA field values (e.g. media_source names, channel labels, event_type, platform values).

Present this inventory to me and wait for my confirmation before proceeding.

## Step 2: Ask me these questions

1. Is the new End User Events report already enabled in Data Locker (enabled from the AppsFlyer UI)? If not, I need to enable it first.
2. What is our new web app's Unified App ID? Format: "website-{domain}" (e.g. website-www.example.com). If I don't know it, I'll get it from the AppsFlyer dashboard before we continue.
3. Do we want a parallel-run period (old and new pipelines side by side, comparing outputs) or a direct cutover? Recommend parallel-run.
4. Should the new pipeline also consume the Conversions report (unique attribution instances, no duplicate rows) or the cross-platform End User Events report (CUID-deduplicated, web+mobile user-level)? Default scope is the platform-level End User Events report, the direct successor of the PBA reports.

## Structural changes to design around

1. **One report instead of two.** PBA split visits and events into two reports. The new End User Events report holds both: visits are rows with end_user_event_type = 'SESSION', events are rows with end_user_event_type = 'IN_APP'. Only IN_APP rows carry a valid event_name; on SESSION rows event_name is empty (PBA wrote "website visit" there).
2. **One schema across platforms.** The report is shared by website, mobile, CTV, and PC. Always filter platform = 'WEBSITE' to isolate web data.
3. **Dual attribution credit, deduplicate.** The same action can appear twice: a primary-credit row and a secondary-credit row (original UA source, for UA-view LTV). Filter is_primary_attribution = true by default to avoid double-counting. Drop the filter only for dedicated UA-view vs retargeting-view analysis using conversion_type.
4. **Hourly instead of daily.** The new report delivers hourly with approximately 2 hours of data freshness. Redesign incremental loads around hourly batches instead of a daily drop.
5. **Organic vs paid.** PBA's media_type ("Paid") is gone. Use the is_organic boolean; never infer organic/paid from media_source.
6. **User counting.** Count and join users with COALESCE(customer_user_id, appsflyer_id_value), the stable customer user ID first, the cookie-based ID as fallback.
7. **Empty values.** STRING fields are '', NUMERIC fields are NULL.

## Field-by-field mapping (PBA → End User Events)

Status legend: Unchanged = same name and data. Renamed = same data, new column. Redefined = data or value format changes (handle with care). Deprecated = no equivalent.

| PBA field | Status | New field / handling |
|---|---|---|
| advertising_id | Deprecated | Mobile/device ID. Cross-platform reports are CUID-based; drop it. |
| af_web_id | Renamed | appsflyer_id_value, same web cookie. |
| amazon_aid | Deprecated | Mobile/device ID, not relevant for web. |
| android_id | Deprecated | Mobile/device ID, not relevant for web. |
| app_id | Deprecated | Mobile cross-platform field. The new web app identifier is unified_app_id ("website-{domain}"), a different concept, not a rename. |
| app_name | Unchanged | app_name. |
| app_version | Deprecated | Mobile cross-platform field. |
| appsflyer_id | Deprecated | Mobile install ID. Note: the new appsflyer_id_value is the web cookie (PBA's af_web_id), NOT this field. |
| attributed_touch_time | Renamed | event_time__attribution. |
| attributed_touch_type | Deprecated | Was a constant. Visit-vs-event now lives in end_user_event_type. |
| bundle_id | Deprecated | Replaced by the Product Line grouping in the cross-platform report. |
| campaign | Renamed | campaign_name. |
| campaign_id | Unchanged | campaign_id. |
| city | Unchanged | city. |
| country_code | Unchanged | country_code. |
| customer_user_id | Unchanged | customer_user_id. |
| device_type | Renamed + values change | device_category, values differ (e.g. "Desktop" → "MOBILE_PHONE" / "TV" style values). Update any value-keyed logic. |
| dma | Unchanged | dma. |
| event_name | Redefined | Events: unchanged. Visits: now empty (PBA wrote "website visit"). Use end_user_event_type = 'SESSION' to identify visits. |
| event_revenue | Renamed | revenue_value_original. |
| event_revenue_currency | Renamed | revenue_currency_original. |
| event_revenue_usd | Renamed | revenue_usd. |
| event_source | Unchanged | event_source. |
| event_time | Unchanged | event_time. |
| event_type | Redefined | "Conversion event" marking no longer exists. Visit-vs-event lives in end_user_event_type (SESSION / IN_APP). |
| event_url | Unchanged | event_url, now also the primary place URL query params (UTMs, etc.) are read from. |
| event_value | Unchanged | event_value. |
| idfa / idfv / imei / oaid | Deprecated | Mobile/device IDs, not relevant for web. |
| install_time | Deprecated | Mobile field. The web user-acquisition time is event_time__conversion, a separate concept. |
| ip | Renamed | ip_address_value (plus ip_address_type for the hashing method). |
| language | Redefined | language, format changes to ISO 639-1 + country code (was e.g. "English"). |
| media_channel | Deprecated | Can be covered by sub_param_1-5 if needed. |
| media_source | Unchanged | media_source (but see value renames below). |
| media_type | Deprecated | Use the is_organic boolean instead. |
| original_url | Renamed | Consolidated into event_url (on visits, event_url equals the original URL). Standalone column gone. |
| platform | Redefined | platform is always "WEBSITE". The OS moved to os_version / user_agent. |
| postal_code | Unchanged | postal_code. |
| query_params | Deprecated | The raw query string lives in event_url; the parsed-JSON column is gone. Re-implement parsing from event_url if needed. |
| referrer | Renamed | http_referrer. |
| region | Renamed | continent, continent code (e.g. "NA"). |
| state | Unchanged | state. |

## Attributed-value changes (update filters and groupings)

The attribution engine resolves traffic sources differently, so some VALUES change even where field names don't:
- media_source renames: doubleclick_int → dv360_int (Google display/video via UTM); "X Ads" → "Twitter" (Twitter via UTM). More PID values now remap to display names (e.g. iossearchads_int → Apple Search Ads, metweb_int → Facebook Ads, tiktokweb_int → tiktokglobal_int, snapweb_int → snapchat_int).
- Channel labels change format: Direct / Organic search / Social media / Email / Ad / Referral / Other become DIRECT / ORGANIC_SEARCH / SOCIAL_MEDIA / EMAIL / AD / REFERRAL / OTHER.
- Click-ID coverage expanded (more networks resolve from click IDs); dclid alone no longer resolves; fbclid is not used.

Scan the codebase for any filter, CASE, join key, or dashboard grouping keyed on these old values and update them.

## Step 3: Build

1. Propose the new ETL design (ingestion, schema, incremental hourly logic, the mandatory filters from "Structural changes").
2. After my approval, implement it, reusing our existing conventions and infrastructure.
3. For every Deprecated field the inventory found in downstream use, list the consumer and propose a resolution (drop, replace with the suggested alternative, or flag to the business owner).

## Step 4: Validate

1. Run both pipelines on the same day range and compare: visits (SESSION rows) vs PBA website visits, events (IN_APP rows) vs PBA website events, and revenue totals.
2. Expect differences, not equality: the new attribution logic is more advanced, so attributed dimensions (media_source, campaign) will not match PBA exactly. Session counting logic is unchanged (30 minutes of activity), so visit volumes should be in the same ballpark.
3. Verify the is_primary_attribution filter is applied everywhere; its absence shows up as inflated event counts.
4. Produce a short migration report: what was mapped, what was dropped, what changed in values, and any open items for the business owner.

Apéndice: Migración de PBA Web-S2S a la nueva API S2S

Si envías eventos a PBA mediante la API de eventos web Server-to-server (Web-S2S), migra a la nueva API S2S. La nueva API admite tanto visitas como eventos, por lo que tu sitio web puede funcionar completamente del lado del servidor; la API S2S de PBA solo aceptaba eventos.

Endpoints y autenticación

PBA Web-S2S Nueva API S2S
URL base https://webs2s.appsflyer.com https://events.appsflyer.com
Llamada de evento POST /v1/{bundleId}/event POST /v2.0/s2s/inapps/app/web/{appId}
Llamada de visita No disponible (las visitas procedían únicamente del SDK web) POST /v2.0/s2s/visits/app/web/{appId}
Llamada de identidad POST /v1/{bundleId}/setcuid (llamada independiente para asociar un CUID con un usuario web) Ninguna; la identidad se envía directamente en el objeto user_id con cada evento/visita
Identificador de la aplicación bundleId (ID del brand bundle) en la ruta appId = el unified_app_id web ("website-{domain}") en la ruta
Autenticación webDevKey dentro del cuerpo JSON en cada llamada Encabezado de autorización que contiene la API key de S2S
Tipo de contenido application/json application/json
Respuesta correcta 200 OK 202 Aceptado

Mapeo de campos del payload

Campo de PBA Web-S2S Nuevo campo S2S Nota
customerUserId user_id.customer_user_id Ahora está anidado en el objeto user_id.
afUserId user_id.appsflyer_id Ahora está anidado en el objeto user_id. Envía al menos uno de los dos, customer_user_id o appsflyer_id; envía ambos siempre que el usuario esté identificado.
webDevKey Eliminado del cuerpo. La autenticación se ha trasladado al encabezado de autorización; la aplicación se identifica mediante appId en la ruta.
eventType (siempre EVENT) Eliminado. El endpoint (/inapps frente a /visits) determina el tipo.
eventName event_name Entre 1 y 64 caracteres; no puede contener @ = + -.
timestamp (Unix ms, 13 dígitos) timestamp (Unix ms) Mismo formato. Se recomienda incluirlo en cada llamada; si se omite, AppsFlyer utiliza la hora de recepción.
eventValue event_value Formato libre; admite un subobjeto custom_parameters.
eventRevenue event_revenue Ahora es opcional (PBA lo requería para los dashboards).
eventRevenueCurrency event_revenue_currency Ahora es opcional (PBA lo requería para los dashboards).
referrer http_referrer Renombrado.
userAgent user_agent Renombrado.
ip ip Sin cambios.
event_url Nuevo. Obligatorio en las visitas; opcional en los eventos.
customer_dedup_id Nuevo. Elimina los duplicados del mismo evento que llegue desde otra fuente (por ejemplo, el SDK web).
email_hashed, phone_number_hashed, phone_number_e164_hashed, first_name_hashed, last_name_hashed Nuevo. SHA256 (hexadecimal en minúsculas de 64 caracteres) de los valores normalizados, para el enriquecimiento de la identidad.

Notas sobre la API S2S

  • Ahora las visitas se procesan del lado del servidor. La API S2S de PBA solo aceptaba eventos; las visitas tenían que proceder del SDK web. El nuevo endpoint /visits permite que el sitio web funcione completamente del lado del servidor.
  • Ya no se necesita la llamada setcuid. PBA requería el SDK web, por lo que se necesitaba una llamada independiente para asociar un CUID a un usuario web. La nueva API incluye la identidad directamente en cada solicitud, por lo que este paso deja de ser necesario.
  • Ventana de datos recibidos con retraso. Envía los eventos y las visitas en tiempo real, preferiblemente en los 30 minutos posteriores a que se produzca el evento, en lugar de agruparlos en una única carga diaria. Los datos que llegan más de 30 minutos después de que finalice el día UTC en el que se produjo el evento se siguen aceptando, pero la hora del evento se sustituye por la hora en la que AppsFlyer los recibió.

Migración del servicio S2S: prompt listo para usar para un agente de programación

Si envías eventos mediante la API Server-to-server de PBA, hemos preparado un prompt listo para usar para tu agente de programación con IA (Claude Code, Cursor o similar). Da al agente acceso al servicio que envía eventos a AppsFlyer y pega el siguiente prompt; contiene el mapeo completo de endpoints y payloads. El prompt guiará al agente para que comprenda tu implementación actual y cree la versión actualizada.

You are migrating a server-side integration from AppsFlyer's legacy PBA Web S2S events API (webs2s.appsflyer.com) to AppsFlyer's new S2S API for Web Performance Measurement (events.appsflyer.com). Everything you need is in this prompt: endpoints, authentication, the complete payload mapping, and the behavior changes. Do not guess anything beyond what is written here; if something is ambiguous, ask me.

## Step 0: Ask me these questions before touching code

1. Should we modify the existing service in place, or create a new service/module alongside it (allowing a parallel-run and clean cutover)? Recommend a new module alongside.
2. Do I know our new web app's Unified App ID? Format: "website-{domain}" (e.g. website-www.example.com). It replaces PBA's bundle ID in the URL path. If I don't know it, I'll get it from the AppsFlyer dashboard before we continue.
3. Do I have the new S2S API key? Authentication moved from the webDevKey in the request body to an Authorization header carrying this key. If I don't have it, I'll retrieve it from the AppsFlyer dashboard.
4. Do we want to send events only (like PBA), or adopt the new visits endpoint too? The new API supports server-side visits, so the website can run fully server-side; PBA accepted events only.
5. Is the AppsFlyer Web SDK still running on our site? (Determines whether visits come from the SDK and whether we need event deduplication between SDK and S2S.)

## Step 1: Learn the current service

Explore the codebase and produce an inventory:
1. Every call site to webs2s.appsflyer.com, the /event calls and any /setcuid calls.
2. The fields populated on each call (customerUserId, afUserId, eventName, eventValue, eventRevenue, timestamp, referrer, userAgent, ip, etc.) and where their values come from.
3. Error handling and monitoring keyed on the 200 OK response.
4. Retry, batching, and queueing behavior.

Present this inventory to me and wait for my confirmation before proceeding.

## What changed: endpoints and authentication

|  | PBA Web-S2S (old) | New S2S API |
|---|---|---|
| Base URL | https://webs2s.appsflyer.com | https://events.appsflyer.com |
| Event call | POST /v1/{bundleId}/event | POST /v2.0/s2s/inapps/app/web/{appId} |
| Visit call | Not available | POST /v2.0/s2s/visits/app/web/{appId} |
| Identity call | POST /v1/{bundleId}/setcuid | None, identity is inline in the user_id object on every call |
| App identifier in path | bundleId (brand bundle ID) | appId = the web Unified App ID ("website-{domain}") |
| Authentication | webDevKey in the JSON body | Authorization header with the S2S API key |
| Content type | application/json | application/json (415 if missing) |
| Success response | 200 OK | 202 Accepted |

## Payload field mapping

| PBA field | New field | Note |
|---|---|---|
| customerUserId | user_id.customer_user_id | Nested in the user_id object. |
| afUserId | user_id.appsflyer_id | Nested in the user_id object. Send at least one of customer_user_id or appsflyer_id; send both whenever the user is identified. |
| webDevKey | (removed) | Auth moved to the Authorization header; the app is identified by appId in the path. |
| eventType (always "EVENT") | (removed) | The endpoint (/inapps vs /visits) determines the type. |
| eventName | event_name | 1-64 chars; cannot contain @ = + - characters. |
| timestamp (Unix ms) | timestamp (Unix ms) | Same format. Recommended on every call; if omitted, AppsFlyer uses receive time. |
| eventValue | event_value | Free-form; supports a custom_parameters sub-object. |
| eventRevenue | event_revenue | Now optional (PBA required it for dashboards). |
| eventRevenueCurrency | event_revenue_currency | Now optional (PBA required it for dashboards). |
| referrer | http_referrer | Renamed. |
| userAgent | user_agent | Renamed. |
| ip | ip | Unchanged. |
| (new) | event_url | Required on visits; optional on events. |
| (new) | customer_dedup_id | Deduplicates against the same event arriving from another source (e.g. the Web SDK). |
| (new) | email_hashed, phone_number_hashed, phone_number_e164_hashed, first_name_hashed, last_name_hashed | SHA256 (64-char lowercase hex) of normalized values, for identity enrichment. |

## Behavior changes and gotchas

1. **appId is the Unified App ID, not the "Web SDK ID".** The AppsFlyer app settings page shows a "Web SDK ID" UUID (the former Web Dev Key, kept for SDK continuity). The S2S path must carry the Unified App ID ("website-{domain}"), never that UUID. A 401 "app not found" usually means a wrong appId in the path or a missing/invalid Authorization header.
2. **Success is 202, not 200.** Update health checks, retries, and alerting accordingly.
3. **Don't validate against the old endpoint.** The legacy endpoint may still return 200, but that response is not proof that data reached Web Performance Measurement. Always verify events actually appear in the new web app's data.
4. **Drop the setcuid flow entirely.** Identity travels inline in the user_id object on every event and visit.
5. **Visits before events.** The system expects a visit before any event from a user; events for a user with no prior visit are classified as organic. Visits come from the Web SDK, or, new, from the /visits endpoint.
6. **Send in real time; don't batch.** Send every event and visit as it happens, preferably within 30 minutes of the event occurring. Data that arrives later than 30 minutes after the end of the UTC day in which the event occurred is still accepted, but its event time is replaced with the receive time, which distorts attribution. Always send a timestamp. Note that the approximately 30-minute attribution delay you may read about is a separate, server-side hold on AppsFlyer's side, nothing for us to implement.
7. **Full server-side option (if we adopt visits).** With no Web SDK, our server owns the web user identifier: generate a stable ID for first-time visitors, persist it as a server-set first-party HTTP cookie (Set-Cookie header, not JavaScript; JS cookies are capped at approximately 7 days on Safari), reuse it on every request, and send it as user_id.appsflyer_id. The visit payload must include event_url (and should include ip, user_agent, http_referrer for attribution quality).
8. **SDK + S2S together.** If both send the same event, populate customer_dedup_id so AppsFlyer keeps one copy.

## Step 2: Build

1. Propose the design: new client/module, config (base URL, appId, API key storage in our secrets manager, never hardcoded), payload builders for events (and visits, if in scope), and the response/retry handling for 202.
2. After my approval, implement it following our existing conventions.
3. Map every field from the inventory through the payload mapping above; flag any field we currently send that has no new equivalent.

## Step 3: Validate

1. Send a test event (and visit, if in scope) and confirm a 202 response.
2. Verify the test data appears in the new web app in AppsFlyer (dashboard or Data Locker); this is the real success signal, not the HTTP response.
3. Confirm event names comply with the new constraints (1-64 chars, no @ = + -).
4. If running in parallel with the old service, compare event volumes between old and new for a few days before cutover, then decommission the old calls including setcuid.

Apéndice: resolución de fuentes de tráfico, PBA vs Medición de performance web

Si los valores de fuente de medios, canal o campaña son diferentes de los que mostraba PBA, aquí te explicamos por qué. Ambos utilizan el mismo enfoque: determinan la fuente de medios a partir de los parámetros de la URL y del referrer de la visita web, siguiendo una lista de prioridades hasta encontrar la primera coincidencia. Sin embargo, varias reglas han cambiado, y estos cambios afectan a los valores que aparecen en los reportes. Fuentes: Reglas de atribución de fuente de medios de PBA y Sobre la resolución de fuentes de tráfico.

Valores de fuente de medios renombrados

Activador Valor de PBA Nuevo valor
Display/vídeo de Google (utm_source=Google + utm_medium= cpm / display/banner/video/listing) doubleclick_int dv360_int
Twitter mediante UTM (twitter + cpc) X Ads Twitter

La medición de performance web también mapea más valores de PID a nombres para mostrar, por ejemplo, iossearchads_int a Apple Search Ads, metweb_int a Facebook Ads, twitterweb_int a Twitter, tiktokweb_int a tiktokglobal_int y snapweb_int a snapchat_int. Los reportes basados en los valores raw anteriores deben adaptarse a los nuevos valores.

Se amplía la cobertura de los Click IDs y se elimina dclid

PBA resolvía tres Click IDs: gclid como googleadwords_int, dclid como doubleclick_int y msclkid como bingsearch_int.

La medición de performance web resuelve muchos más: gclid/wbraid/gbraid como googleadwords_int, msclkid como bingsearch_int, twclid como Twitter, vmcid como yahoogemini_int, sccid como snapchat_int, li_fat_id como linkedin_int, ttclid como tiktokglobal_int, tbclid como taboola_int, ob_click_id/dicbo como outbrain_int, yclid como yandex_int y rdt_cid como reddit_int.

Advertencia

Hay dos cambios de comportamiento: dclid ya no se utiliza por sí solo para la resolución (PBA lo resolvía como doubleclick_int) y fbclid no se utiliza de forma explícita.

Se amplían las reglas personalizadas de UTM

El mapeo personalizado de utm_source + utm_medium es más amplio en la medición de performance web: incluye más sinónimos de medium (paid, paid_search, paid-search, etc.) y añade compatibilidad con TikTok, Snapchat y Pinterest, más allá de las reglas de PBA limitadas a un único medium. Cuando ninguna regla personalizada coincide, ambos utilizan como alternativa el valor raw de utm_source.

Channel

Los valores de canal también cambian de formato: de Direct / Organic search / Social media / Email / Ad / Referral / Other en PBA a los nuevos DIRECT / ORGANIC_SEARCH / SOCIAL_MEDIA / EMAIL / AD / REFERRAL / OTHER. Los filtros y las agrupaciones basados en las etiquetas anteriores deben actualizarse.