Registro de eventos
Los eventos son la materia prima de todo en Joryio: los segmentos, activadores de recorridos, filtros de campaña y la analítica se basan en los eventos que envían tus apps. Esta guía cubre las convenciones y mecánicas compartidas por todos los SDK: web, iOS, Android y React Native. Para la instalación y configuración específica de cada plataforma, consulta las páginas de cada SDK.
Cómo funciona el registro
Cada SDK sigue el mismo flujo:
- Llamas a
track(eventName, properties)en tu app. - El SDK pone el evento en cola localmente (con persistencia sin conexión) y lo envía en lotes: por defecto cada cinco segundos o cada 50 eventos, lo que ocurra primero.
- El lote se entrega a
POST /v1/track/batch, autenticado con la clave de SDK de la app (formatojry_sdk_<plataforma>_<aleatorio>, una por app; consulta Resumen de apps). - El servidor resuelve el usuario (anónimo o identificado), almacena los eventos y los distribuye a segmentos, activadores de recorridos y analítica.
Un lote puede contener como máximo 500 eventos. El SDK establece las marcas de tiempo al realizar la llamada; el servidor acepta milisegundos de época o cadenas ISO-8601 y usa la hora del servidor si falta la marca de tiempo o no es válida (para que un reloj incorrecto del dispositivo nunca provoque el rechazo de un evento). Todos los SDK las envían en UTC con precisión de milisegundos (ISO-8601 como 2026-10-07T09:15:02.123Z, o epoch en milisegundos), así que dos llamadas dentro del mismo segundo conservan su orden.
Sesiones
Una sesión solo empieza por el ciclo de vida normal del SDK - la app pasa a primer plano, o una página se carga en la web - y eso siempre envía un evento Session Start. Leer la sesión (su id o sus datos) y las sincronizaciones de mensajes in-app no tienen efectos secundarios: nunca inician ni prolongan una sesión, así que consultar la sesión desde tu código no puede crear sesiones sin Session Start.
Convenciones de nomenclatura de eventos
El nombre de un evento es una cadena de hasta 255 caracteres. Más allá de eso, Joryio no impone un formato, pero la coherencia importa porque los nombres de eventos permiten encontrarlos después en los creadores de segmentos, activadores de recorridos y el Explorador de eventos.
Recomendaciones:
- Usa Title Case con espacios. Los eventos integrados de Joryio siguen la taxonomía de comercio electrónico estándar del sector (objeto + acción en pasado, Title Case):
Product Viewed,Product Added,Checkout StartedyOrder Completedde las integraciones de tienda. Los eventos personalizados en Title Case (Trial Started,Signup Completed) mantienen uniforme el catálogo. Snake case también funciona, pero elige una convención y respétala; mezclarlas crea eventos que parecen duplicados. - Nombra la acción, no la interfaz.
Order Completedsobrevive a un rediseño;Green Button Clicked, no. - Usa objeto + verbo en pasado.
Subscription Upgraded,Video Played,Search Performed. - Mantén la variabilidad en propiedades, no en nombres. Un evento
Product Viewedcon una propiedadcategoryes mejor que cincuenta eventosViewed <Category>: los segmentos y activadores coinciden primero con el nombre y luego filtran propiedades. - Evita eventos demasiado genéricos.
Clickedsin propiedades no te dice nada sobre lo que puedes actuar.
Los nombres de eventos distinguen mayúsculas y minúsculas: order_placed y Order_Placed son dos eventos distintos.
Los nombres de evento que empiezan por canvas., message. o in_app. (con cualquier combinación de mayúsculas) están reservados para los eventos de recorrido y de mensajería de Joryio, y un evento que envíes con uno de ellos se rechaza - consulta Nombres de evento reservados.
Propiedades y tipos de datos
Las propiedades son un objeto JSON asociado a cada evento. Se acepta cualquier valor JSON:
| Tipo | Ejemplo | Notas |
|---|---|---|
| Cadena | "currency": "USD" | También para fechas, como cadenas ISO-8601 |
| Número | "total": 149.99 | Enteros y decimales |
| Booleano | "first_order": true | |
| Matriz | "item_ids": ["SKU-1", "SKU-2"] | |
| Objeto | "shipping": { "method": "express" } | Se permiten objetos anidados |
Límites por evento en el servidor:
| Límite | Valor |
|---|---|
| Longitud del nombre de evento | 255 caracteres |
| Tamaño total de propiedades (JSON serializado) | 50 KB |
| Claves de propiedad de nivel superior | 200 |
| Profundidad de anidamiento | 5 niveles |
| Eventos por solicitud de lote | 500 |
El tamaño es la longitud de las propiedades serializadas como JSON, en caracteres (51.200), y la profundidad de anidamiento cuenta el propio objeto de propiedades como nivel 1. Los límites se aplican a las propiedades que envía tu app - las propiedades $ que añade el servidor no cuentan. Un evento que supera un límite, o con un nombre vacío o demasiado largo, se rechaza para siempre: la respuesta del lote lo indica en rejectedIndices (consulta Respuestas de lote) y el resto del lote se almacena. Los SDK comprueban los mismos límites al llamar a track() y descartan ese evento ahí mismo, con una línea de log.
Los eventos que superan estos límites se rechazan. Los nombres de propiedades siguen el mismo consejo que los nombres de eventos: elige una convención (product_id, no a veces productId) y mantén los tipos estables; un order_id que es cadena en un evento y número en otro vuelve poco fiable el filtrado.
NaN, Infinity y -Infinity no son números JSON. Todos los SDK descartan una propiedad o un atributo cuyo valor sea uno de ellos en el momento de la llamada, registran un aviso y envían el resto de la llamada sin esa clave - ninguna excepción llega a tu código, nada se guarda en el dispositivo y un valor erróneo nunca retiene otras escrituras. Lo mismo vale para las propiedades personalizadas de los trackers de comercio electrónico. En la web, un valor de atributo que no se puede serializar (un objeto circular, un BigInt, una función) se descarta del mismo modo.
Las propiedades con prefijo $ (como $platform, $session_id, $app_id) se añaden automáticamente: algunas mediante los SDK ($device_id, datos de dispositivo en Session Start) y otras mediante la canalización de ingesta de Joryio al recibir el evento ($app_id, $session_id, $is_identified). Trata este prefijo como reservado y no lo uses para tus propias propiedades.
Reglas de nombres para atributos de usuario
Las claves de atributos (el objeto que pasas a setAttributes / setAttribute)
tienen dos restricciones adicionales, y una clave que incumpla cualquiera de ellas
se descarta; el resto de la llamada se guarda con normalidad:
| No permitido | Por qué |
|---|---|
Un . en cualquier parte de la clave | El punto se interpreta como separador de RUTA, no como carácter. "profile.email" se guardaría anidado como profile: { email }, así que un segmento sobre profile.email no coincidiría con nada. |
Un $ inicial | Reservado, igual que en las propiedades de evento anteriores. |
| Una clave vacía | No hay nada que guardar. |
Las claves se descartan en lugar de renombrarse a propósito: renombrarlas informaría de un éxito mientras deja tus datos donde nunca los consultas.
identify frente a track
Envía siempre los números de teléfono en formato internacional completo (E.164): +, el código de país y luego el número - por ejemplo +972501234567. Un número sin + se lee con el país predeterminado para números de teléfono del espacio de trabajo; si no hay ninguno, o los dígitos se pueden leer de dos formas, se guarda sin verificar y no se usa para mensajes. Cómo se tratan los números de teléfono
Las dos llamadas principales cumplen funciones distintas:
identify(userId)indica quién es el usuario. Vincula el dispositivo o sesión actual con tu ID de usuario estable y fusiona cualquier historial anónimo con ese perfil. Los atributos establecidos consetAttributesdescriben al usuario (email, plan y nombre) y viven en el perfil.track(eventName, properties)indica qué ocurrió. Las propiedades describen el evento, no al usuario, y son inmutables una vez registradas.
Reglas prácticas:
- Llama a
identifyen cuanto conozcas al usuario: al iniciar sesión y al iniciar la app si se restaura una sesión. Usa el mismo ID de usuario en todas las plataformas para que la actividad web y móvil llegue a un solo perfil (consulta Seguimiento multiplataforma). - Antes de
identify, los eventos se registran con un ID anónimo. Cuando identificas más tarde, el servidor fusiona el historial anónimo con el perfil identificado, para que no se pierdan eventos previos al registro (primera visita y atribución). - No existe una llamada
alias(userId)aparte:identify(userId)vincula el historial anónimo y da nombre a la persona. Para registrar los IDs de la persona en otros sistemas (un ID de CRM, un número de fidelización), usaaddAlias(label, name): funciona también antes de iniciar sesión, y los alias pasan al perfil identificado cuando el visitante se identifica. - Coloca los datos de cada ocurrencia en propiedades de evento (
total,coupon) y los datos duraderos de la persona en atributos (plan,lifetime_value). - Llama a
reset()al cerrar sesión para que el siguiente usuario del dispositivo no herede el perfil.
El mismo evento en todos los SDK
La llamada track tiene intencionadamente la misma estructura en todos los SDK. Aquí está el mismo evento Order Completed en las cuatro plataformas.
- Web (JS)
- iOS (Swift)
- Android (Kotlin)
- React Native
import JoryioSDK from '@joryio/web-sdk';
const joryio = new JoryioSDK({ sdkKey: 'jry_sdk_web_...' });
joryio.track('Order Completed', {
orderId: 'ORD-2024-001',
total: 149.99,
currency: 'USD',
item_count: 3,
coupon: 'SAVE10',
});
Joryio.shared.track("Order Completed", properties: [
"orderId": "ORD-2024-001",
"total": 149.99,
"currency": "USD",
"item_count": 3,
"coupon": "SAVE10"
])
Joryio.track("Order Completed", mapOf(
"orderId" to "ORD-2024-001",
"total" to 149.99,
"currency" to "USD",
"item_count" to 3,
"coupon" to "SAVE10"
))
import Joryio from '@joryio/react-native-sdk';
Joryio.track('Order Completed', {
orderId: 'ORD-2024-001',
total: 149.99,
currency: 'USD',
item_count: 3,
coupon: 'SAVE10',
});
Como el nombre y las propiedades son idénticos, una condición de segmento o activador de recorrido coincide con el evento sin importar de qué plataforma proceda.
Para actividad estándar de comercio electrónico (vistas de producto, carritos, checkout y pedidos), prioriza los rastreadores de comercio electrónico integrados en los SDK: emiten los nombres de evento estandarizados que esperan las funciones de comercio electrónico de Joryio. Consulta la guía de seguimiento de comercio electrónico para todos los SDK.
Qué ocurre en el servidor
Cuando se acepta un lote, cada evento:
- Se almacena en el almacén de analítica, asociado al perfil de usuario resuelto (identificado o anónimo).
- Se evalúa frente a activadores de recorridos. Un recorrido cuyo activador de entrada coincide con el nombre de evento (y filtros de propiedad) inscribe al usuario de inmediato; así comienzan los recorridos de «carrito abandonado» o «bienvenida».
- Alimenta segmentos. Las condiciones de segmento basadas en eventos («realizó
Order Completeden los últimos 30 días») se actualizan desde el flujo de eventos y las campañas dirigidas a esos segmentos recogen el cambio. - Aparece en analítica: Explorador de eventos, embudos y analítica por app.
- Puede actualizar el perfil. Algunos eventos tienen efectos secundarios gestionados por el servidor; por ejemplo, los eventos
Session Startactualizan el registro de dispositivo y establecen atributos comocountry(derivado de la IP de la solicitud).
Dos comportamientos del servidor que conviene conocer:
- Marcado de bots. Las solicitudes de agentes de usuario de bots conocidos se marcan (los eventos se etiquetan y la analítica los excluye); las llamadas que modifican perfiles como
identifyysetAttributesse omiten para bots. Por ello, el tráfico de navegadores sin interfaz de tu automatización de pruebas puede no crear perfiles. - Validación flexible. Los campos extra desconocidos de la carga se eliminan en vez de rechazarse, para que una diferencia de versión del SDK no descarte tus eventos.
Contrato del servidor para las solicitudes del SDK
La lógica de cola y de reintentos de los SDK se basa en el comportamiento del servidor que se describe a continuación. Si llamas a estos endpoints por tu cuenta (una integración propia o un proxy), trata las respuestas de la misma forma.
Respuestas de lote
POST /v1/track/batch acepta como máximo 500 eventos y un cuerpo de solicitud de hasta 1 MB.
| Respuesta | Significado | Qué hacer |
|---|---|---|
200 con success: true | Se almacenaron todos los eventos | Elimina el lote |
200 con success: false | Algunos o todos los eventos no se almacenaron. Dos listas indican sus posiciones (desde 0) en el lote enviado: failedIndices (un fallo de nuestro lado) y rejectedIndices (rechazados para siempre - superan los límites por evento o tienen un nombre no válido). partial: true significa que el resto sí se almacenó; failed cuenta ambas listas | Reintenta solo los eventos de failedIndices - reintentar un evento ya almacenado es seguro, clientEventId evita duplicados. Descarta los eventos de rejectedIndices: volver a enviarlos nunca funcionará |
400 | El cuerpo no superó la validación (por ejemplo más de 500 eventos, o un evento mal formado) | No lo reenvíes tal cual - divide el lote |
413 | El cuerpo supera 1 MB | Divide el lote |
401 | La clave del SDK no es válida o está inactiva, o (con SDK Authentication activado) una solicitud que nombra un userId no lleva token X-Joryio-Auth, o este ha caducado o es de otro usuario - cuerpo error: "sdk_authentication_error" y un reason: missing, expired, invalid o sub_mismatch (el token es de otro usuario). Las solicitudes anónimas no necesitan token | Renueva el token y reintenta. Los eventos en cola de un usuario que ya no es el que ha iniciado sesión no pueden obtener token: ante cualquier reason (missing, expired, invalid o sub_mismatch), descártalos. Solo los fallos del usuario con la sesión iniciada llegan a tu manejador de errores de SDK Authentication, así que los restos de un usuario anterior nunca cierran la sesión de quien usa la app ahora |
402 con code: "SDK_INGESTION_SUSPENDED" | La ingesta de eventos está en pausa para la cuenta | Deja de enviar durante esta sesión, conserva las escrituras y no cuentes el 402 para su límite de reintentos; las retiradas se siguen enviando, incluso cuando esperan en la cola detrás de una escritura rechazada |
403 | La política de identidad de la app rechazó la solicitud: strict SDK identity rechaza las escrituras anónimas (code: "SDK_STRICT_IDENTITY_REQUIRED") y SDK Authentication rechaza un anonymousId que pertenece a un usuario identificado (code: "SDK_ANONYMOUS_ID_IDENTIFIED"). Las retiradas son la excepción a strict SDK identity - consulta Las retiradas se entregan siempre | Deja de enviar durante esta sesión |
429 | Límite de frecuencia. La cabecera Retry-After indica la espera en segundos (también retryAfter en el cuerpo) | Espera y reintenta |
5xx | Error del servidor | Reintenta con backoff |
Un fallo parcial tiene este aspecto:
{ "success": false, "partial": true, "processed": 47, "failed": 3, "failedIndices": [3, 17], "rejectedIndices": [21], "error": "failed to track: 2 (retry), rejected: 1 (do not retry)" }
Respuestas de las demás rutas de escritura
/v1/identify, /v1/attributes, /v1/alias/add, /v1/subscriptions/channel, /v1/subscriptions/group, /v1/push/register y /v1/push/unregister no tienen resultado parcial, así que el código de estado por sí solo dice qué pasó. Solo la ruta de lotes de arriba responde a un fallo con 200.
| Respuesta | Significado | Qué hacer |
|---|---|---|
200 con success: true | Hecho | Confirma |
400 | La solicitud es incorrecta tal como se envió - por ejemplo sin userId ni anonymousId, sin atributos válidos, web push sin subscription o un endpoint no permitido, o un registro push de iOS cuyo token no es un device token de APNs (por ejemplo un token de FCM - registra tokens de FCM solo en Android) | No la reintentes - descártala y regístrala |
401, 402, 403, 429 | Como en la tabla de lotes de arriba | Como arriba |
404 | Suscripciones, solo en servidores anteriores: todavía no hay perfil para ese userId o anonymousId. El servidor actual crea en su lugar el perfil que falta, igual que /v1/alias/add | Reintenta después de enviar los eventos en cola, unas pocas veces como máximo |
403 con code: "verified_identity_required" | Solo endpoints del SDK (/v1/subscriptions/channel, /v1/subscriptions/group): volver a suscribir a una persona que se dio de baja explícitamente (su baja o la de una lista, un STOP, una baja en su email o teléfono - también de otro contacto) desde un sujeto no verificado - un userId sin SDK Authentication, o un anonymousId que pertenece a una persona identificada. Una primera alta se acepta; una lista con doble opt-in responde pending: true y envía el email de confirmación. Darse de baja siempre está permitido | No reintentes - activa SDK Authentication o vuelve a suscribir desde tu servidor con la API |
409 con code: "ALIAS_TAKEN" | Solo /v1/alias/add: el alias pertenece a otro perfil | No reintentes |
400 con code: "ALIAS_LABEL_RESERVED" | /v1/alias/add y la API de aliases: las etiquetas shopify_customer, woocommerce_customer y magento_customer están reservadas - las escribe la integración de la tienda | No reintentes |
503 con code: "SDK_TEMPORARILY_UNAVAILABLE" | Un fallo de nuestro lado | Conserva la escritura y reintenta con backoff |
Cómo cuentan los reintentos los SDK
Todos los SDK cuentan los intentos fallidos de una escritura de la misma forma. Estar sin conexión, un timeout, 402, 429 y 5xx nunca cuentan: la escritura espera y se envía cuando desaparece la causa. Un 404 (servidores antiguos, aún sin perfil) cuenta para un límite de 5, y cualquier otra respuesta reintentable - 401 del usuario con sesión iniciada, o success: false - para un límite de 10, tras el cual la escritura se descarta. 400, 403, 413 y 422 la descartan de inmediato.
Aliases para visitantes anónimos
POST /v1/alias/add recibe { "anonymousId", "userId", "aliasLabel", "aliasName" }, con userId opcional. Sin userId el alias se asocia al perfil anónimo; cuando el visitante hace identify, el alias pasa al perfil identificado junto con el resto del historial anónimo.
Un alias (aliasLabel + aliasName) pertenece a un único perfil por workspace. Si ya pertenece a otro perfil no se mueve: la respuesta es 409 con code: "ALIAS_TAKEN", y reintentar no servirá. SDK Authentication funciona aquí como en todas partes: un visitante anónimo no tiene token que mostrar, así que se acepta un addAlias anónimo; en cuanto se envía un userId, debe estar vinculado al token, como en identify. Solo Strict SDK identity rechaza los alias anónimos. Las etiquetas shopify_customer, woocommerce_customer y magento_customer están reservadas: guardan los ids de cliente de una tienda Shopify, WooCommerce o Magento conectada y solo las escribe la integración de la tienda, así que un addAlias con una de ellas se rechaza con 400 y code: "ALIAS_LABEL_RESERVED".
Identidad y registro de clics en mensajes in-app
/v1/in-app/sync y /v1/in-app/track reciben userId (una vez identificado el visitante) y anonymousId como campos separados. El servidor evalúa el perfil identificado y, mientras un identify sigue en camino, el perfil anónimo. Nunca pongas una dirección de email en externalId.
action en /v1/in-app/track sale de un vocabulario fijo (displayed, clicked, dismissed, converted y algunos sinónimos); una acción desconocida no se registra y la respuesta indica reason: "unknown_action". Para registrar qué botón o enlace se pulsó, envía su nombre en elementId (hasta 128 caracteres) junto a action: "clicked". Se guarda en el evento in_app.clicked y el informe de clics de la campaña agrupa por él.
Rutas de seguimiento de push
Los acuses de entrega de push (POST /track/push/delivered/<trackingId>) y los clics (GET /track/push/click/<trackingId>) se sirven en la raíz del host de la API, no bajo /api. Los SDK las construyen a partir del scheme y el host del endpoint configurado. Las mismas rutas también responden bajo /api/track/push/..., así que una versión que conservó la ruta /api sigue informando.
El registro de exclusión
Cuando una persona se excluye del seguimiento (optOut() en todos los SDK), el SDK escribe en el perfil el atributo tracking_opted_out: true, y optIn() lo devuelve a false. Es un atributo booleano como cualquier otro: filtra por él en un segmento ("tracking_opted_out es true") para dejar fuera de una audiencia a quienes se excluyeron, o para contarlos. Es un registro de la decisión, no su aplicación - el SDK deja de enviar por sí mismo - y las versiones del SDK anteriores a 1.3.0 lo enviaban como $tracking_opted_out, que el servidor ahora guarda bajo la misma clave tracking_opted_out.
Las retiradas se entregan siempre
Cuatro escrituras solo reducen lo que se recoge o se envía: una suscripción de canal puesta en unsubscribed, un unsubscribe de grupo, el registro de exclusión tracking_opted_out: true y /v1/push/unregister. Los SDK las mantienen en cola y las reintentan cuando la persona se ha excluido del seguimiento, cuando el consentimiento de seguimiento está denegado o pendiente, y después de wipeData(); el servidor las acepta en esas mismas situaciones:
- Ninguna comprobación de consentimiento o de exclusión en el servidor las rechaza: una persona excluida puede darse de baja igualmente.
- Strict SDK identity no las rechaza cuando vienen de un visitante anónimo. Solo la dirección contraria - suscribirse,
tracking_opted_out: falseo el registro enviado junto con cualquier otro atributo - sigue necesitando un usuario identificado con token verificado. - Una cuenta en pausa no las rechaza: mientras la ingesta de eventos está en pausa (
402,SDK_INGESTION_SUSPENDED), el servidor las sigue aceptando y los SDK las siguen enviando, de modo que el «no» de una persona queda registrado sea cual sea el estado de facturación de la cuenta. - Un token caducado sigue sirviendo para ellas. Con SDK Authentication activado, una retirada que nombra un
userIdse acepta con el token de ese usuario hasta 30 días después de su caducidad. La firma y el sujeto (sub) se comprueban como siempre; solo se relaja la caducidad, y solo para estas cuatro escrituras - todo lo demás necesita un token vigente. Así cerrar sesión no pierde nada: cuando un usuario cierra sesión (reset()), el SDK conserva su token - cifrado donde la plataforma tiene almacenamiento seguro (el Keychain de iOS, las preferencias cifradas de Android; en la web, local storage junto a la cola) - con sus escrituras pendientes, y lo borra cuando la última se entrega o se descarta, de modo que una baja pulsada justo antes de cerrar sesión llega igualmente, incluso tras reiniciar la app. - Sesión iniciada mientras el seguimiento está rechazado:
identify(userId)con el consentimiento denegado o con la persona excluida no envía nada, no registra nada y no escribe ningún perfil, pero el SDK guarda eluserIden el dispositivo con un único fin - una retirada hecha entonces lo lleva, así que se da de baja el perfil de la propia persona y no uno anónimo nuevo. Cuando se concede el consentimiento o la persona vuelve a aceptar el seguimiento, se ejecuta elidentifynormal.reset()ywipeData()lo olvidan. /v1/push/unregisterno necesita tokenX-Joryio-Authaunque el cuerpo nombre unuserId: lo que se elimina es el token push o el endpoint de web push, así que el dispositivo de un usuario que cerró sesión deja de recibir push aunque su token ya no exista.- La única regla que sigue aplicando: con SDK Authentication activado, un
anonymousIdque pertenece a un usuario identificado no puede retirar el consentimiento de ese usuario sin su token (403,SDK_ANONYMOUS_ID_IDENTIFIED). Una escritura de suscripción que nombra unuserIdsigue necesitando el token de ese usuario, como en todas partes.
Cuando la cola del dispositivo está llena, se descarta primero la escritura más antigua que no sea una retirada.
Verificación y depuración
Comprobar que llegó un evento
- Activa el evento en tu app.
- En el panel de Joryio, abre Analítica → Explorador de eventos. Filtra por nombre de evento y deberías verlo en segundos después de que el SDK envíe su lote (intervalo predeterminado: cinco segundos).
- Para una vista por app, abre Configuración → Apps y haz clic en Ver analítica en la app. Muestra el total de eventos, la marca de tiempo del último evento y los nombres de eventos principales, con lo que confirmas rápidamente si está llegando algo desde esa clave de SDK.
Si los eventos no aparecen
- Fuerza un envío. Los eventos se agrupan en lotes; llama a
flush()(disponible en todos los SDK) para enviar inmediatamente en lugar de esperar el temporizador. - Activa el registro de depuración. Todos los SDK tienen una opción
enableDebugque registra cada evento en cola y cada solicitud de red con su respuesta. - Comprueba la clave de SDK. Debe coincidir con la plataforma de la app:
jry_sdk_web_...para SDK web,jry_sdk_ios_...para iOS yjry_sdk_android_...para Android. Una clave regenerada invalida la anterior de inmediato. El SDK de Android (también React Native y Unity en Android) indica su plataforma al servidor, y una clave de una app de otra plataforma se rechaza con401ycode: "SDK_KEY_PLATFORM_MISMATCH". - Comprueba que la app está activa en Configuración → Apps: se rechazan los eventos enviados con la clave de una app desactivada.
- Observa la respuesta de red. Una respuesta de lote incluye
successy, ante un fallo parcial,failedIndices(no almacenados, se reintentarán) yrejectedIndices(rechazados para siempre). Las propiedades demasiado grandes (más de 50 KB, más de 200 claves o más de cinco niveles de anidamiento) son la causa habitual de eventos rechazados. - Solo web: los endpoints de seguimiento del SDK permiten cualquier origen (
Access-Control-Allow-Origin: *), por lo que los errores CORS suelen indicar unapiEndpointincorrecto o una extensión de navegador bloqueadora; consulta la consola. Confirma también que el sitio se ejecuta mediante HTTPS para que funcione la persistencia de cola basada en localStorage. - ¿Pruebas desde automatización? Recuerda el marcado de bots anterior: verifica con un navegador o dispositivo real.