Web SDK - Guía de implementación de JavaScript nativo

Documento

Visión general

INFORMACIÓN: Web Attribution es una función para empresas. Póngase en contacto con su Customer Success Manager para activar esta función en su cuenta.

Esta guía explica cómo implementar Singular WebSDK utilizando JavaScript nativo. Este método proporciona el seguimiento más fiable y se recomienda para la mayoría de las implementaciones, ya que no es bloqueado por los bloqueadores de anuncios comunes.

IMPORTANTE

  • No implemente ambos métodos, JavaScript nativo y Google Tag Manager. Elija sólo uno para evitar el seguimiento duplicado.
  • Singular WebSDK está diseñado para ejecutarse en el navegador del usuario. Requiere acceso a funciones del navegador como localStorage y el Modelo de objetos del documento (DOM) para funcionar correctamente. No intente ejecutar el SDK en el lado del servidor (por ejemplo, a través de Next.js SSR o node.js), ya que esto provocará fallos en el rastreo, puesto que los entornos de servidor no proporcionan acceso a las API del navegador.

Requisitos previos

Antes de empezar, asegúrese de que dispone de

  • SDK Key & SDK Secret:
  • ID de producto:
    • Qué es: Un nombre único para su sitio web, idealmente utilizando el formato DNS inverso (por ejemplo, com.website-name).
    • Por qué es importante: Este ID asocia el sitio web como una App en Singular y debe coincidir con el bundleID de la Web App que aparece en su Página de Apps en Singular.
  • Permiso para editar el código HTML de su sitio Web.
  • Acceso para añadir JavaScript en la sección <head> de sus páginas.
  • Una lista de los eventos que desea controlar. Consulte nuestros Eventos estándar de Singular: Lista completa y Eventos recomendados por vertical para obtener ideas.

Pasos de implementación


Paso 1: Añadir el script de la biblioteca SDK

Añada el siguiente fragmento de código a la sección <head> de todas las páginas de su sitio web. Colóquelo lo antes posible, idealmente cerca de la parte superior de la etiqueta <head>.

CONSEJO Si añade el script lo antes posible, se asegurará de que Singular JavaScript Library esté disponible para cualquier función de Singular en el código fuente de su página.

Latest VersionSpecific VersionUsing NPMNext.js / React
<script src="https://web-sdk-cdn.singular.net/singular-sdk/latest/singular-sdk.js"></script>

Paso 2: Inicializar el SDK

  • Inicialice siempre el SDK cada vez que se cargue una página en el navegador.
  • La inicialización es necesaria para todas las funciones de atribución y seguimiento de eventos de Singular.
  • La inicialización activa un evento __PAGE_VISIT__ evento.
  • El evento __PAGE_VISIT__ se utiliza para generar una nueva sesión en el servidor cuando se cumplen las siguientes condiciones:
    • El usuario llega con nuevos datos publicitarios en la URL (como parámetros UTM o WP), o
    • La sesión anterior ha expirado (después de 30 minutos de inactividad).
    • Las sesiones se utilizan para medir la retención de usuarios y apoyar la atribución de reenganche.
  1. Crear una función de inicialización y llamar a esta función en DOM Ready después de cargar la página.
  2. Asegúrese de que la inicialización se produce antes de que se notifique cualquier otro evento Singular.
  3. Para aplicaciones de una sola página (SPA), inicialice Singular SDK en la primera carga de página y luego llame a la función Singular Page Visist window.singularSdk.pageVisit() en cada cambio de ruta que represente una nueva vista de página.
Basic InitializationNext.js / React

Inicialización básica DOM-Ready

Añada un receptor de eventos para llamar a initSingularSDK()en DOMContentLoaded

/**
 * Initializes the Singular SDK with the provided configuration.
 * @param {string} sdkKey - The SDK key for Singular.
 * @param {string} sdkSecret - The SDK secret for Singular.
 * @param {string} productId - The product ID for Singular.
 * @example
 * initSingularSDK(); // Initializes SDK with default config
 */
function initSingularSDK() {
  var config = new SingularConfig('sdkKey', 'sdkSecret', 'productId');
  window.singularSdk.init(config);
}

/**
 * Triggers Singular SDK initialization when the DOM is fully parsed.
 */
document.addEventListener('DOMContentLoaded', function() {
  initSingularSDK();
});

Inicialización básica con propiedades globales

Debido a que el SDK Singular no está inicializado, debe implementar la función personalizada para establecer las Propiedades Globales en el localstorage del navegador. Consulte la función personalizada setGlobalPropertyBeforeInit(). Una vez implementada, puede establecer las propiedades como se indica a continuación antes de la inicialización del SDK.

/**
 * Initializes the Singular SDK with the provided configuration.
 * @param {string} sdkKey - The SDK key for Singular.
 * @param {string} sdkSecret - The SDK secret for Singular.
 * @param {string} productId - The product ID for Singular.
 * @example
 * initSingularSDK(); // Initializes SDK with default config
 */
function initSingularSDK() {
  var sdkKey = 'sdkKey';
  var sdkSecret = 'sdkSecret';
  var productId = 'productId';
  
  // Set global properties before SDK initialization
  setGlobalPropertyBeforeInit(sdkKey, productId, 'global_prop_1', 'test', false);
  setGlobalPropertyBeforeInit(sdkKey, productId, 'global_prop_2', 'US', true);
  
  // Initialize SDK
  var config = new SingularConfig('sdkKey', 'sdkSecret', 'productId');
  window.singularSdk.init(config);
}

/**
 * Triggers Singular SDK initialization when the DOM is fully parsed.
 */
document.addEventListener('DOMContentLoaded', function() {
  initSingularSDK();
});

Inicialización básica con enrutamiento de aplicación de una sola página (SPA)

Escenario Qué hacer

Carga de la primera página

Llamar a window.singularSdk.init(config)

Navegar a una nueva ruta/página

Llamar a window.singularSdk.pageVisit()

En la carga inicial en SPA

No llamar a window.singularSdk.pageVisit() (La inicialización proporcionará el evento de visita a la primera página).

Ejemplo para SPAs (React)

import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

/**
 * Initializes the Singular SDK with the provided configuration.
 * @param {string} sdkKey - The SDK key for Singular.
 * @param {string} sdkSecret - The SDK secret for Singular.
 * @param {string} productId - The product ID for Singular.
 * @example
 * // Initialize the Singular SDK
 * initSingularSDK();
 */
function initSingularSDK() {
  var config = new SingularConfig('sdkKey', 'sdkSecret', 'productId');
  window.singularSdk.init(config);
}

/**
 * Tracks a page visit event with the Singular SDK on route changes.
 * @example
 * // Track a page visit
 * trackPageVisit();
 */
function trackPageVisit() {
  window.singularSdk.pageVisit();
}

/**
 * A React component that initializes the Singular SDK on mount and tracks page visits on route changes.
 * @returns {JSX.Element} The component rendering the SPA content.
 * @example
 * // Use in a React SPA with react-router-dom
 * function App() {
 *   const location = useLocation();
 *   useEffect(() => {
 *     initSingularSDK();
 *   }, []);
 *   useEffect(() => {
 *     trackPageVisit();
 *   }, [location.pathname]);
 *   return <div>Your SPA Content</div>;
 * }
 */
function App() {
  const location = useLocation();

  useEffect(() => {
    initSingularSDK();
  }, []); // Run once on mount for SDK initialization

  useEffect(() => {
    trackPageVisit();
  }, [location.pathname]); // Run on route changes for page visits

  return (
    <div>Your SPA Content</div>
  );
}

export default App;

Inicialización Básica Usando un Callback de Inicialización

Si necesitas ejecutar código después de que el SDK esté listo (por ejemplo, para obtener el Singular Device ID), establece un callback con .withInitFinishedCallback():

/**
 * Initializes the Singular SDK with a callback to handle initialization completion.
 * @param {string} sdkKey - The SDK key for Singular.
 * @param {string} sdkSecret - The SDK secret for Singular.
 * @param {string} productId - The product ID for Singular.
 * @example
 * initSingularSDK(); // Initializes SDK and logs device ID
 */
function initSingularSDK() {
  var config = new SingularConfig('sdkKey', 'sdkSecret', 'productId')
    .withInitFinishedCallback(function(initParams) {
      var singularDeviceId = initParams.singularDeviceId;
      // Example: Store device ID for analytics
      console.log('Singular Device ID:', singularDeviceId);
      // Optionally store in localStorage or use for event tracking
      // localStorage.setItem('singularDeviceId', singularDeviceId);
    });

  window.singularSdk.init(config);
}

/**
 * Triggers Singular SDK initialization when the DOM is fully parsed.
 */
document.addEventListener('DOMContentLoaded', function() {
  initSingularSDK();
});
  • Sustituya 'sdkKey' por su clave SDK actual.
  • Sustituya 'sdkSecret' por su SDK Secret real.
  • Sustituya 'productId' con su ID de producto real. Debe parecerse a: com.website-name y debe coincidir con el valor BundleID en la página Apps en la plataforma Singular.

Opciones de configuración

Mejore la configuración del WebSDK encadenando métodos de .with para habilitar funciones adicionales.

Por ejemplo, para admitir el seguimiento entre subdominios mediante la persistencia del ID de dispositivo Singular (SDID) en una cookie o para incluir un ID de usuario personalizado para un visitante del sitio que regresa con un estado de inicio de sesión activo:

Config with Options
var domain = 'website-name.com';
var config = new SingularConfig('sdkKey','sdkSecret','productId')
  .withAutoPersistentSingularDeviceId(domain)
  .withCustomUserId(userId);

Referencia del método SingularConfig

Estos son todos los métodos ".with" disponibles.

Método Descripción Más información
.withCustomUserId(customId) Enviar el ID de usuario a Singular Establecer el ID de usuario
.withProductName(productName) Un nombre opcional para mostrar el producto
.withLogLevel(logLevel) Configurar el nivel de registro: 0 - Ninguno (por defecto); 1 - Advertencia; 2 - Información; 3 - Depuración.
.withSessionTimeoutInMinutes(timeout) Configurar el tiempo de espera de la sesión en minutos (por defecto: 30 minutos)

.withAutoPersistentSingularDeviceId(domain) Activar el seguimiento automático entre subdominios Persistencia automática mediante cookies

.withPersistentSingularDeviceId(singularDeviceId)

Activar el seguimiento manual entre subdominios Establecer manualmente el ID de dispositivo singular
.withInitFinishedCallback(callback) Invocar una función callback cuando se completa la inicialización del SDK Invocar una función de devolución de llamada cuando se ha completado la inicialización

Lista de comprobación para desarrolladores

  • Reúna sus credenciales del SDK y su ID de producto.
  • Decida si necesita alguna configuración personalizada (ID de usuario, tiempo de espera, etc.).
  • Construya una función Singular Intitialization y un objeto SingularConfig utilizando los ejemplos anteriores.
  • Compruebe siempre que la inicialización sólo se activa una vez al cargar la página.

CONSEJO Para configuraciones avanzadas como el seguimiento de búsqueda orgánica, puede que necesite implementar JavaScript personalizado (por ejemplo, para ajustar los parámetros de consulta) antes de que se inicialice Singular SDK. Asegúrese de que su código personalizado se ejecuta antes de la función de inicialización de Singularpara que los cambios se capturen correctamente. Puede encontrar más información sobre cómo implementar el seguimiento de búsqueda orgánica aquí.


Paso 3: Seguimiento de eventos

Después de inicializar el SDK, puede realizar un seguimiento de los eventos personalizados cuando los usuarios realicen acciones importantes en su sitio web.

¡IMPORTANTE! Singular no bloquea los eventos duplicados. Es responsabilidad de los desarrolladores añadir protecciones contra los refrescos de página o la duplicación. Se recomienda incorporar algún método de deduplicación específico para los eventos de ingresos para evitar datos de ingresos erróneos. Consulte "Paso 5: Prevención de eventos duplicados" a continuación para ver un ejemplo...

Basic EventConversion EventRevenue Event

Seguimiento básico de eventos

Realice el seguimiento de un evento simple o añada atributos personalizados utilizando JSON válido para proporcionar más contexto sobre el evento:

// Basic event tracking
var eventName = 'page_view';
window.singularSdk.event(eventName);
      
// Optional: With attributes for more context
var eventName = 'sng_content_view';
var attributes = {
  key1: 'value1', // First custom attribute
  key2: 'value2'  // Second custom attribute
};
window.singularSdk.event(eventName, attributes);

Patrones comunes de implementación de eventos

Page Load EventsButton Click EventsForm Submission Events

Eventos de carga de página

El mecanismo de seguimiento de eventos de carga de página utiliza JavaScript para supervisar cuándo se carga completamente una página Web y, a continuación, activa un evento de análisis mediante Singular SDK. En concreto, aprovecha el método window.addEventListener('load', ...) para detectar cuándo se han cargado todos los recursos de la página (p. ej., HTML, imágenes, secuencias de comandos). Tras este evento, se llama a la función event de Singular SDK para registrar un evento personalizado con atributos asociados, lo que permite el seguimiento de las interacciones del usuario con fines analíticos, como el control de las visitas a la página o las acciones del usuario, como los registros. Este mecanismo se utiliza habitualmente en la analítica web para capturar datos sobre el comportamiento de los usuarios, como visitas a páginas o acciones específicas, con atributos personalizables para obtener información detallada.

El mecanismo puede adaptarse para rastrear un evento específico (por ejemplo, un evento page_view ) con atributos personalizados, como se muestra a continuación. La estructura del código se mantiene limpia y modular, con el nombre del evento y los atributos definidos por separado para mayor claridad y facilidad de mantenimiento.

/**
 * Tracks a registration event with the Singular SDK when the page fully loads.
 * @param {string} eventName - The name of the event to track (e.g., 'page_view').
 * @param {Object} attributes - A JSON object with custom event attributes.
 * @param {string} attributes.key1 - First custom attribute (e.g., 'value1').
 * @param {string} attributes.key2 - Second custom attribute (e.g., 'value2').
 */
window.addEventListener('load', function() {
  var eventName = 'page_view';
  var attributes = {
    key1: 'value1', // First custom attribute
    key2: 'value2'  // Second custom attribute
  };
  window.singularSdk.event(eventName, attributes);
});

Paso 4: Establecer el ID de usuario del cliente

Puede enviar su ID de usuario interno a Singular utilizando un método del SDK de Singular.

NOTA: Si utiliza la solución Cross-Device de Singular, debe recopilar el ID de usuario en todas las plataformas.

  • El ID de usuario puede ser cualquier identificador y no debe exponer PII (Personally Identifiable Information). Por ejemplo, no debe utilizar la dirección de correo electrónico, el nombre de usuario o el número de teléfono de un usuario. Singular recomienda utilizar un valor hash único sólo para sus datos de origen.
  • El valor de ID de usuario que se pasa a Singular también debe ser el mismo ID de usuario interno que se captura en todas las plataformas (Web/Móvil/PC/Consola/Offline).
  • Singular incluirá el ID de usuario en las exportaciones a nivel de usuario, ETL y devoluciones internas de BI (si está configurado). El ID de usuario es un dato de origen y Singular no lo comparte con terceros.
  • El valor del ID de usuario, cuando se establece con el método Singular SDK, persistirá hasta que se desactive utilizando el método logout() o hasta que se elimine el almacenamiento local del navegador. Cerrar o actualizar el sitio web no elimina el ID de usuario.
  • En modo privado/incógnito, el SDK no puede mantener el ID de usuario porque el navegador borra automáticamente el almacenamiento local cuando se cierra.

Para establecer el ID de usuario, utilice el método login(). Para desestablecerlo (por ejemplo, si el usuario "cierra sesión" en la cuenta), llame al método logout().

NOTA: Si varios usuarios utilizan un único dispositivo, recomendamos implementar un flujo de cierre de sesión para establecer y desestablecer el ID de usuario para cada inicio y cierre de sesión.

Si ya conoce el ID de usuario cuando se inicializa Singular SDK en el sitio web, establezca el ID de usuario en el objeto config. De esta forma, Singular puede tener el ID de usuario desde la primera sesión. Sin embargo, el ID de usuario no suele estar disponible hasta que el usuario se registra o realiza un inicio de sesión. En ese caso, llame a login() una vez finalizado el flujo de registro.

CONSEJO Utilice el mismo ID de usuario de cliente que utiliza en sus SDK móviles. Esto permite la atribución entre dispositivos y proporciona una visión completa del comportamiento del usuario en todas las plataformas.

Mejores prácticas para el ID de usuario del cliente:

  • Establézcalo en cuanto el usuario inicie sesión o se registre.
  • Utilice el mismo ID en todas las plataformas web y móviles
  • No utilice información de identificación personal (correo electrónico, número de teléfono).
  • Utilice su ID de usuario interno o ID de base de datos
  • Envíelo como una cadena, aunque sea numérica:
Set the User IDUnset the User ID
// After user logs in or signs up
var userId = 'user_12345';
window.singularSdk.login(userId);

Paso 5: Prevención de eventos duplicados

¡IMPORTANTE! Este es uno de los errores de implementación más comunes. Sin las salvaguardas adecuadas, los eventos pueden dispararse múltiples veces, inflando sus métricas.

Evitar la duplicación de eventos de ingresos en una aplicación web es importante para obtener análisis precisos y evitar la sobreinformación de conversiones. El objetivo es garantizar que cada evento único sólo se active una vez por sesión o visita a la página, incluso si el usuario vuelve a cargar la página o activa varios envíos sin darse cuenta.

  • Se genera una clave única para cada evento (basada en los detalles del evento y en atributos personalizados opcionales).
  • Esta clave se almacena en el sessionStorage del navegador (o en el localStorage para la deduplicación persistente).
  • Antes de enviar, el código comprueba si ya se ha disparado un evento de ingresos con la misma carga útil.
  • Si no es así, envía el evento y guarda la clave.
  • Si es así, bloquea la repetición y notifica al usuario o al desarrollador.
Using Session Storage Method

Uso del método de almacenamiento de sesión

// Sends a revenue event to Singular WebSDK, preventing duplicates in the same session
// @param {string} eventName - Name of the revenue event (defaults to "web_purchase")
// @param {number} amount - Revenue amount (defaults to 0)
// @param {string} currency - Currency code (defaults to "USD", normalized to uppercase)
// @param {object} [attributes] - Optional key-value pairs for additional event data
function sendRevenueEvent(eventName, amount, currency, attributes) {
  // Normalize inputs: set defaults and ensure currency is uppercase
  eventName = eventName ? eventName : "web_purchase";
  currency = currency ? currency.toUpperCase() : "USD";
  amount = amount ? amount : 0;

  // Create a payload object to standardize event data
  var payload = {
    eventName: eventName,
    amount: amount,
    currency: currency,
    attributes: attributes ? attributes : null // Null if no attributes provided
  };

  // Generate a unique key for sessionStorage by hashing the payload
  // This ensures deduplication of identical events in the same session
  var storageKey = 'singular_revenue_' + btoa(JSON.stringify(payload));

  // Check if the event was already sent in this session to prevent duplicates
  if (!sessionStorage.getItem(storageKey)) {
    // Mark event as sent in sessionStorage
    sessionStorage.setItem(storageKey, 'true');

    // Send revenue event to Singular SDK, including attributes if provided and valid
    if (attributes && typeof attributes === 'object' && Object.keys(attributes).length > 0) {
      window.singularSdk.revenue(eventName, currency, amount, attributes);
    } else {
      // Fallback to basic revenue event without attributes
      window.singularSdk.revenue(eventName, currency, amount);
    }
    // Log event details for debugging
    console.log("Revenue event sent:", payload);
  } else {
    // Log and alert if a duplicate event is detected
    console.log("Duplicate revenue event prevented:", payload);
    alert("Duplicate revenue event prevented!");
  }
}

Paso 6: Probar su implementación

Después de implementar el SDK, verifique que funciona correctamente utilizando las herramientas de desarrollo de su navegador.

Verificar que el SDK está cargado

  1. Abra su sitio web en un navegador
  2. Abra las herramientas para desarrolladores (F12 o haga clic con el botón derecho → Inspeccionar)
  3. Vaya a la pestaña Consola
  4. Escriba typeof singularSdk y pulse Intro
  5. Debería ver un objeto "function", no "undefined".

Verifique las solicitudes de red

  1. Abra Herramientas de desarrollo (F12)
  2. Vaya a la pestaña Red
  3. Vuelva a cargar la página
  4. Filtre por singular o sdk-api
  5. Busque solicitudes a sdk-api-v1.singular.net
  6. Haga clic en una solicitud para ver los detalles
  7. Compruebe que el código de estado es 200
  8. Compruebe que la carga útil de la solicitud contiene su ID de producto. Éste estará en el parámetro "i" de la carga útil.

ÉXITO Si ve solicitudes a sdk-api-v1.singular.net con el código de estado 200, su SDK está enviando datos correctamente a Singular.

Verificar eventos

  1. Active un evento en su sitio web (haga clic en un botón, envíe un formulario, etc.).
  2. En la pestaña Red, busque una nueva solicitud a sdk-api-v1.singular.net.
  3. Haga clic en la solicitud y vea la pestaña Payload o Solicitud
  4. Compruebe que el parámetro del nombre del evento "n" aparece en la solicitud.

    event_test.png

Para las pruebas de extremo a extremo, utilice la consola de pruebas

Puede probar la integración Web SDK utilizando la Consola SDK disponible en el panel de control de Singular.

  1. En la plataforma Singular, vaya a Herramientas de desarrollador > Consola de pruebas.
  2. Haga clic en Añadir dispositivo.
  3. Seleccione la plataforma"Web" e introduzca el ID del dispositivo Singular.
  4. Obtenga el ID de dispositivo Singular de la carga útil del navegador. Compruebe la captura de pantalla anterior para localizar el SDID en un evento.
  5. La consola de pruebas debe permanecer en estado activo mientras realiza las pruebas en una ventana independiente. La consola no mostrará los eventos que se activen mientras esté cerrada o desconectada.
  6. Una vez añadido el SDID, puede recargar la página web y activar algunos eventos. El evento "__PAGE_VISIT__" y otros eventos (si se han disparado) se mostrarán en la consola SDK.
  7. Utilizar los registros de exportación(Reports & Insights Export Logs) es otra forma de verificar los resultados de las pruebas. Este conjunto de datos tiene un retraso de 1 hora.

Paso 7: Implementar el reenvío de Web a App

Reenvío de atribuciones de Web a App

Utilice Singular WebSDK para realizar un seguimiento de los recorridos de los usuarios desde su sitio web a su aplicación móvil, lo que permite una atribución precisa de la campaña web a las instalaciones y reutilizaciones de la aplicación móvil. Siga estos pasos para configurar el reenvío de la web a la aplicación, incluida la compatibilidad con códigos QR para usuarios de escritorio.

  1. Siga nuestra Guía de Reenvío de Atribución de Sitio Web a Aplicación Móvil para configurar Singular WebSDK para la atribución web móvil.
  2. Para el seguimiento de Web a aplicación de escritorio:
    • Complete la configuración de la guía anterior.
    • Utilice Singular Mobile Web-to-App Link (por ejemplo, https://yourlink.sng.link/...) y la función WebSDK buildWebToAppLink(link) con una biblioteca dinámica de códigos QR como QRCode.js.
    • Genera un código QR en tu página web que codifique el enlace. Los usuarios de escritorio pueden escanearlo con su dispositivo móvil para abrir la aplicación, pasando los datos de la campaña para su atribución.
    • Explora nuestra Demo Singular WebSDK para ver un ejemplo práctico de generación de códigos QR y gestión de enlaces web-a-aplicación.

CONSEJO Las vistas web del navegador de la aplicación móvil (como las utilizadas por Facebook, Instagram y TikTok) pueden hacer que el ID de dispositivo de Singular cambie si un usuario cambia al navegador nativo del dispositivo, lo que interrumpe la atribución.

Para evitarlo, utilice siempre el formato de enlace de seguimiento Singular adecuado para cada red publicitaria:


Demostración de Singular WebSDK

A continuación se muestra una implementación sencilla pero completa que utiliza la API Singular WebSDK documentada. Esta muestra proporciona una separación clara para eventos personalizados, eventos de conversión, eventos de ingresos y soporte de enlace web-a-aplicación utilizando el reenvío web-a-aplicación soportado por WebSDK.

Puede ejecutar este código en un archivo HTML local o modificarlo en su sitio para la integración avanzada y la solución de problemas.

Demostración de Singular WebSDK (código fuente)
#

Copie y pegue el código de abajo como un archivo HTML y ábralo en su navegador.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
  <title>Singular WebSDK Demo</title>
  <script src="https://cdn.jsdelivr.net/npm/qrcodejs/qrcode.min.js"></script>
  <script src="https://web-sdk-cdn.singular.net/singular-sdk/latest/singular-sdk.js"></script>

  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', Roboto, sans-serif;
      line-height: 1.6;
      color: #1a1d29;
      background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
      padding: 40px 20px;
      min-height: 100vh;
    }

    .container {
      max-width: 800px;
      margin: 0 auto;
      background: white;
      padding: 48px;
      border-radius: 16px;
      box-shadow: 0 4px 24px rgba(30, 41, 59, 0.08);
      border: 1px solid rgba(148, 163, 184, 0.1);
    }

    .header-brand {
      display: flex;
      align-items: center;
      margin-bottom: 32px;
      padding-bottom: 24px;
      border-bottom: 2px solid #f1f5f9;
    }

    .brand-dot {
      width: 12px;
      height: 12px;
      background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
      border-radius: 50%;
      margin-right: 12px;
    }

    h1 {
      font-size: 32px;
      font-weight: 700;
      color: #0f172a;
      letter-spacing: -0.025em;
    }

    h2 {
      font-size: 24px;
      font-weight: 600;
      margin: 48px 0 24px 0;
      color: #1e293b;
      padding-bottom: 12px;
      border-bottom: 2px solid #e2e8f0;
      position: relative;
    }

    h2:before {
      content: '';
      position: absolute;
      bottom: -2px;
      left: 0;
      width: 60px;
      height: 2px;
      background: linear-gradient(90deg, #6366f1, #8b5cf6);
    }

    p {
      margin-bottom: 18px;
      color: #475569;
      font-size: 15px;
    }

    strong {
      color: #1e293b;
      font-weight: 600;
    }

    .form-group {
      margin-bottom: 24px;
    }

    .radio-group {
      display: flex;
      gap: 24px;
      margin-bottom: 24px;
      padding: 16px;
      background: #f8fafc;
      border-radius: 12px;
      border: 1px solid #e2e8f0;
    }

    .radio-group label {
      display: flex;
      align-items: center;
      gap: 10px;
      cursor: pointer;
      font-size: 14px;
      font-weight: 500;
      color: #475569;
      transition: color 0.2s ease;
    }

    .radio-group label:hover {
      color: #1e293b;
    }

    input[type="radio"] {
      width: 20px;
      height: 20px;
      cursor: pointer;
      accent-color: #6366f1;
    }

    input[type="text"] {
      width: 100%;
      padding: 14px 18px;
      font-size: 15px;
      font-weight: 400;
      border: 2px solid #e2e8f0;
      border-radius: 12px;
      transition: all 0.2s ease;
      background: #fafbfc;
      color: #1e293b;
    }

    input[type="text"]:focus {
      outline: none;
      border-color: #6366f1;
      background: white;
      box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
    }

    input[type="text"]::placeholder {
      color: #94a3b8;
      font-weight: 400;
    }

    button {
      padding: 14px 32px;
      font-size: 15px;
      font-weight: 700;
      color: #fff;
      background: linear-gradient(90deg, #2363f6 0%, #1672fe 100%);
      border: none;
      border-radius: 4px;  /* Pill shape */
      cursor: pointer;
      box-shadow: 0 2px 8px rgba(35, 99, 246, 0.12);
      transition: background 0.2s, transform 0.2s;
      letter-spacing: 0.03em;
    }
    button:hover {
      background: linear-gradient(90deg, #1672fe 0%, #2363f6 100%);
      transform: translateY(-2px);
      box-shadow: 0 8px 24px rgba(22, 114, 254, 0.18);
    }
    button:active {
      transform: translateY(0);
    }

    a {
      color: #6366f1;
      text-decoration: none;
      font-weight: 600;
      transition: all 0.2s ease;
      position: relative;
    }

    a:hover {
      color: #4f46e5;
    }

    a:after {
      content: '';
      position: absolute;
      width: 0;
      height: 2px;
      bottom: -2px;
      left: 0;
      background: #6366f1;
      transition: width 0.2s ease;
    }

    a:hover:after {
      width: 100%;
    }

    ol {
      margin-left: 10px;
      margin-bottom: 18px;
    }

    ol li {
      margin-left: 10px;
    }

    .info-box {
      background: linear-gradient(135deg, #eff6ff 0%, #f0f9ff 100%);
      border-left: 4px solid #6366f1;
      padding: 20px;
      margin: 24px 0;
      border-radius: 12px;
      border: 1px solid rgba(99, 102, 241, 0.1);
    }

    .info-box p {
      color: #1e40af;
      margin-bottom: 0;
    }

    .section-label {
      font-size: 12px;
      font-weight: 700;
      color: #6b7280;
      margin-bottom: 10px;
      text-transform: uppercase;
      letter-spacing: 1px;
    }

    .button-group {
      display: flex;
      align-items: center;
      gap: 16px;
      flex-wrap: wrap;
    }

    .analytics-badge {
      display: inline-flex;
      align-items: center;
      gap: 8px;
      background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
      color: white;
      padding: 6px 12px;
      border-radius: 20px;
      font-size: 12px;
      font-weight: 600;
      margin-bottom: 16px;
    }

    .feature-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      gap: 16px;
      margin: 24px 0;
    }

    .feature-item {
      padding: 16px;
      background: #f8fafc;
      border-radius: 10px;
      border: 1px solid #e2e8f0;
      text-align: center;
    }

    .feature-item strong {
      display: block;
      color: #6366f1;
      font-size: 14px;
      margin-bottom: 4px;
    }

    .metric-dot {
      width: 8px;
      height: 8px;
      background: #10b981;
      border-radius: 50%;
      display: inline-block;
      margin-right: 8px;
      animation: pulse 2s infinite;
    }

    @keyframes pulse {
      0%, 100% { opacity: 1; }
      50% { opacity: 0.5; }
    }
  </style>

</head>

<body>
  <div class="container">
    <div class="header-brand">
      <div class="brand-dot"></div>
      <h1>Singular WebSDK Demo</h1>
    </div>

    <p>
      Below is a simple but comprehensive implementation using the documented Singular WebSDK API. 
      This sample provides clear separation for <strong>custom events</strong>, <strong>conversion events</strong>, <strong>revenue events</strong>, and <strong>web-to-app link support using WebSDK supported Web-to-App forwarding</strong>. </p>
      <p>You can run this code in a local HTML file or modify it in your site for advanced integration and troubleshooting.
    </p>

    <h2>SDK Initialization Testing</h2>
    <ol>
      <li>Open your browsers developer tools.</li>
      <li>Inspect the Network tab and filter for "sdk-api-v1.singular.net"</li>
      <li>Refresh this page, and examin the payload of the request.</li>
    </ol>


    <hr>

    <h2>User Authentication</h2>
    <p>User authentication is key to supporting cross-device attribution. Pass the common User ID value that would also be sent to Singular from mobile SDKs for cross-device continuity.</p>
    <div class="form-group">
      <div class="section-label">Simulate User Authentication</div>
      <input type="text" id="userId" placeholder="Enter a User ID"/>
    </div>
    <div class="form-group">
      <div class="button-group">
        <button onclick="setUserId()">Set the User ID (Login)</button>
        <button onclick="unsetUserId()">Unset the User ID (Logout)</button>
      </div>
    </div>

    <hr>

    <h2>SDK Event Testing</h2>
    <div class="form-group">
      <div class="section-label">Event Type Selection</div>
      <div class="radio-group">
        <label>
          <input type="radio" name="eventType" value="custom" checked onchange="handleRevenueFields(this)">
          <span>Custom Event</span>
        </label>
        <label>
          <input type="radio" name="eventType" value="conversion" onchange="handleRevenueFields(this)">
          <span>Conversion Event</span>
        </label>
        <label>
          <input type="radio" name="eventType" value="revenue" onchange="handleRevenueFields(this)">
          <span>Revenue Event</span>
        </label>
      </div>
    </div>

    <div class="form-group">
      <div class="section-label">Event Configuration</div>
      <input type="text" id="eventName" placeholder="Enter event name"/>
    </div>

    <div class="form-group" id="currencyGroup" style="display:none">
      <input type="text" id="currency" placeholder="Currency code (e.g., USD, EUR)"/>
    </div>

    <div class="form-group" id="amountGroup" style="display:none">
      <input type="text" id="amount" placeholder="Revenue amount (e.g., 29.99)"/>
    </div>

    <div class="form-group" id="attributesGroup" style="display:none">
      <input type="text" id="attributes" placeholder='Optional attributes (JSON, e.g. {"color":"blue"})'/>
    </div>

    <div class="form-group">
      <button id="send" onclick="sendCustomEvent()">Send Event to Singular</button>
    </div>

    <hr>
    <h2>Web-to-App Forwarding</h2>
    <div class="form-group">
    <ol>
      <li><a href="https://support.singular.net/hc/es-419/articles/360042283811" target="_blank">Learn more about Web-to-App Forwarding</a></li>
    <li>In the page source code you will find the mobile web-to-app base link "<strong>https://seteam.sng.link/D0mvs/y3br?_smtype=3</strong>" used for demonstration purposes.</li>
    <li>Replace all occurances of the link with your own Singular Mobile Web-to-App link to test functionality.</li>
    <li>Add Singular Web Parameters to the URL and refresh the page. (example: "<strong>?wpsrc=SingularTest&wpcn=MyCampaign</strong>").</li>
  </ol>

    <hr>
    <div class="section-label">Mobile Web-to-App Test</div>
    <div class="form-group">
      <div class="button-group">
        <button onclick="displayWebLink()">Display Constructed Web-to-App Link</button>
        <button onclick="testWebLink()">Test Web-to-App Link</button>
      </div>
    </div>
  </div>
    <hr>
    <div class="section-label">Desktop Web-to-App Configuration</div>
    <div class="form-group">
    <p>Use a dynamic QR Code generation library like <a href="https://davidshimjs.github.io/qrcodejs/" target="_blank">QRCode.js</a> to build and display a QR code using the Singular Mobile Web-to-App link with constructed campaign parameters.</p>
    <div id="qrcode"></div>
    </div>

  </div>
  
  <script>
    /**
     * Initializes the Singular SDK with the provided configuration.
     * @param {string} sdkKey - The SDK key for Singular (e.g., 'se_team_9b3431b0').
     * @param {string} sdkSecret - The SDK secret for Singular (e.g., 'bcdee06e8490949422c071437da5c5ed').
     * @param {string} productId - The product ID for Singular (e.g., 'com.website').
     * @example
     * // Initialize the Singular SDK
     * initSingularSDK('se_team_9b3431b0', 'bcdee06e8490949422c071437da5c5ed', 'com.website');
     */
    function initSingularSDK() {
      var sdkKey = 'se_team_9b3431b0';
      var sdkSecret = 'bcdee06e8490949422c071437da5c5ed';
      var productId = 'com.website';
      
      var config = new SingularConfig(sdkKey, sdkSecret, productId)
        .withInitFinishedCallback(function(initParams) {
          var singularDeviceId = initParams.singularDeviceId;
          console.log('Singular Device ID:', singularDeviceId);
          // Optionally store for use in events
          // sessionStorage.setItem('singularDeviceId', singularDeviceId);
        });

      window.singularSdk.init(config);
      generateDesktopWebToAppQRCode(); // Generate QR code after initialization
    }

    /**
     * Triggers Singular SDK initialization when the DOM is fully parsed.
     * @example
     * document.addEventListener('DOMContentLoaded', function() {
     *   initSingularSDK();
     * });
     */
    document.addEventListener('DOMContentLoaded', function() {
      initSingularSDK();
    });


    // Fires a custom event using the Singular SDK
    function sendCustomEvent() {
      var eventName = document.getElementById('eventName').value;
      window.singularSdk.event(eventName);
    }

    // Fires a conversion event using the Singular SDK
    function sendConversionEvent() {
      var eventName = document.getElementById('eventName').value;
      window.singularSdk.conversionEvent(eventName);
    }

    // Fires a revenue event only once per session with deduplication
    // Parameters:
    //   - eventName: string (name of event), default "web_purchase" if blank
    //   - amount: revenue amount, default 0 if blank
    //   - currency: string, defaults to "USD" if blank
    //   - attributes: optional object with extra payload
    function sendRevenueEvent(eventName, amount, currency, attributes) {
      // Fill in defaults and normalize values
      eventName = eventName ? eventName : "web_purchase";
      currency = currency ? currency.toUpperCase() : "USD";
      amount = amount ? amount : 0;

      // Create a unique key based on event data for deduplication
      // btoa(JSON.stringify(...)) ensures order consistency in local/sessionStorage
      var payload = {
        eventName: eventName,
        amount: amount,
        currency: currency,
        attributes: attributes ? attributes : null
      };
      var storageKey = 'singular_revenue_' + btoa(JSON.stringify(payload));

      // Only fire event if no identical event has already fired in this tab/session
      if (!sessionStorage.getItem(storageKey)) {
        sessionStorage.setItem(storageKey, 'true');
        if (attributes && typeof attributes === 'object' && Object.keys(attributes).length > 0) {
          // Fire event with attributes payload
          window.singularSdk.revenue(eventName, currency, amount, attributes);
        } else {
          // Fire simple revenue event
          window.singularSdk.revenue(eventName, currency, amount);
        }
        console.log("Revenue event sent:", payload);
      } else {
        // Duplicate detected, don't send to SDK
        console.log("Duplicate revenue event prevented:", payload);
        alert("Duplicate revenue event prevented!");
      }
    }

    // Collects form values, validates attributes JSON, and calls sendRevenueEvent()
    // Ensures only valid values are sent and blocks on malformed JSON
    function fireFormRevenueEvent() {
      var eventName = document.getElementById('eventName').value;
      var currency = document.getElementById('currency').value;
      var amount = document.getElementById('amount').value;
      
      var attributesRaw = document.getElementById('attributes').value;
      var attributes = null;
      if (attributesRaw) {
        try {
          // Try to parse the optional attributes field from JSON string
          attributes = JSON.parse(attributesRaw);
        } catch (e) {
          alert("Optional attributes must be valid JSON.");
          return; // Stop if invalid JSON
        }
      }

      // Calls main revenue logic for deduplication and event sending
      sendRevenueEvent(eventName, amount, currency, attributes);
    }

    // Controls which form fields are visible depending on selected event type
    // Revenue events require currency, amount, and attributes fields
    function handleRevenueFields(sender) {
      var isRevenue = sender.value === "revenue";
      document.getElementById("currencyGroup").style.display = isRevenue ? "block" : "none";
      document.getElementById("amountGroup").style.display = isRevenue ? "block" : "none";
      document.getElementById("attributesGroup").style.display = isRevenue ? "block" : "none";

      // Dynamically assign the event button's onclick handler
      var send = document.getElementById("send");
      send.onclick = (sender.value === "custom") ? sendCustomEvent
        : (sender.value === "conversion") ? sendConversionEvent
        : fireFormRevenueEvent; // Only fires revenue logic for "revenue"
    }

    // Opens demo Singular web-to-app link in a new tab/window
    function testWebLink() {
      var singularWebToAppBaseLink = 'https://seteam.sng.link/D0mvs/y3br?_smtype=3';
      window.open(singularWebToAppBaseLink);
    }

    // Displays constructed Singular web-to-app link with campaign parameters
    function displayWebLink() {
      var singularWebToAppBaseLink = 'https://seteam.sng.link/D0mvs/y3br?_smtype=3';
      var builtLink = window.singularSdk.buildWebToAppLink(singularWebToAppBaseLink);
      console.log("Singular Web-to-App Link: ", builtLink);
      alert("Singular Web-to-App Link: " + builtLink);
    }

    // Generates QR code for desktop deep linking using Singular Mobile Web-to-App link
    function generateDesktopWebToAppQRCode() {
      var singularWebToAppBaseLink = 'https://seteam.sng.link/D0mvs/y3br?_smtype=3';
      const value = window.singularSdk.buildWebToAppLink(singularWebToAppBaseLink);
      new QRCode(document.getElementById("qrcode"), {
        text: value,
        width: 128,
        height: 128,
        colorDark: "#000",
        colorLight: "#fff",
        correctLevel: QRCode.CorrectLevel.H
      });
    }

    // Simulate user authentication and send login event
    function setUserId() {
      var userId = document.getElementById('userId').value;
      window.singularSdk.login(userId);
      console.log("Singular User ID is Set to: " + userId);
      window.singularSdk.event("sng_login");
    }

    // Simulate user logout and unset Singular user ID
    function unsetUserId() {
      window.singularSdk.logout();
      console.log("Singular User ID is Unset");
    }
  </script>
</body>
</html>

Temas avanzados

Banners Singulares

Activar Banners Singulares
#

INFO: Los banners singulares son una función empresarial. Para obtener más información sobre esta función, póngase en contacto con su Customer Success Manager.

Los banners singulares pueden mostrarse en su sitio web móvil para guiar a los usuarios de la web sin problemas a su aplicación y mostrar el contenido más relevante de la aplicación. Una vez que habilite Singular Banners en su sitio web, su organización puede diseñar, implementar y mantener fácilmente banners a través de la interfaz de usuario de Singular Banners.

Artículos relacionados

Guía de implementación paso a paso

  1. Añada el Singular Native JavaScript WebSDK Script a su sitio web.

    Siga la guía de integración anterior para añadir el Singular Native JavaScript WebSDK a su sitio web antes de proceder a la implementación de los Banners.

  2. Conceda a Singular permiso para acceder a los datos de sugerencias del cliente

    Con las limitaciones sobre los datos del agente de usuario en los navegadores web basados en Chromium, implementadas en febrero-marzo de 2023, los anunciantes ahora necesitan obtener datos de sugerencias del cliente, así como dar permiso a Singular WebSDK para recibir estos datos. Para más información, consulte las Preguntas frecuentes sobre banners.

    El sitio web necesita:

    • Empezar a solicitar Sugerencias del cliente (accept-ch header).
    • Conceder permiso a Singular (como tercero) para que el navegador envíe a Singular las sugerencias del cliente en las peticiones de obtención de banners (permissions-policy header).

    Añada las siguientes cabeceras de respuesta a la respuesta de carga de la página:

    accept-ch:
    sec-ch-ua-model,
    sec-ch-ua-platform-version,
    sec-ch-ua-full-version-list
    
    permissions-policy:
    ch-ua-model=("https://sdk-api-v1.singular.net"),
    ch-ua-platform-version=("https://sdk-api-v1.singular.net"),
    ch-ua-full-version-list=("https://sdk-api-v1.singular.net")
  3. Añada la opción Banner config a la inicialización del WebSDK.

    Singular Banners muestra avisos de descarga de aplicaciones inteligentes en su sitio web para móviles, conservando la atribución de la fuente de marketing original. Cuando se activa con la compatibilidad web-a-aplicación, el SDK rastrea qué red publicitaria o campaña llevó al usuario a su sitio, y luego atribuye la posterior instalación de la aplicación a esa fuente original.

    /**
     * Initialize Singular SDK with Banner support and web-to-app attribution.
     * This enables smart banners that direct mobile web users to download your app
     * while preserving the original marketing source (UTM parameters, ad network, etc.)
     * for proper attribution when the app is installed.
     * @param {string} sdkKey - Your Singular SDK Key
     * @param {string} sdkSecret - Your Singular SDK Secret
     * @param {string} productId - Your Product ID (e.g., com.your-website)
     */
    function initSingularSDK() {
      // Enable web-to-app tracking to preserve attribution data
      var bannersOptions = new BannersOptions().withWebToAppSupport();
      
      // Configure SDK with banner support
      var config = new SingularConfig(sdkKey, sdkSecret, productId)
        .withBannersSupport(bannersOptions);
      
      // Initialize the SDK
      window.singularSdk.init(config);
      
      // Display the smart banner
      window.singularSdk.showBanner();
    }
    
    // Call on DOM ready
    document.addEventListener('DOMContentLoaded', function() {
      initSingularSDK();
    });
  4. Reshow Banner on Page Route (Sólo aplicaciones de una página)

    Si su aplicación es de una sola página, debe ocultar y volver a mostrar el banner en cada ruta de página. Esto garantiza que Singular ofrezca el banner adecuado para su experiencia web.

    Para ocultar y volver a mostrar el banner, utilice el siguiente código:

    window.singularSdk.hideBanner();
    window.singularSdk.showBanner();
  5. [Opción avanzada] Personalizar la configuración de enlaces

    Singular proporciona una forma de personalizar los enlaces del banner mediante código.

    Para personalizar los enlaces:

    • Cree un objeto LinkParams y utilice una o varias de las funciones que se indican a continuación. Haga esto antes de llamar a window.singularSdk.showBanner().
    • A continuación, pase el objeto LinkParams cuando llame a window.singularSdk.showBanner().

    Ejemplo:

    // Define a LinkParams object
    let bannerParams = new LinkParams();
    
    // Configure link options (see details on each option below)
    bannerParams.withAndroidRedirect("androidRedirectValue");
    bannerParams.withAndroidDL("androidDLParamValue");
    bannerParams.withAndroidDDL("androidDDLparamValue");
    bannerParams.withIosRedirect("iosRedirectValue");
    bannerParams.withIosDL("iosDLValue");
    bannerParams.withIosDDL("iosDDLValue");
    
    // Show the banner with the defined options
    window.singularSdk.showBanner(bannerParams);

    Lista de opciones:

    Método Descripción
    withAndroidRedirect Pasa un enlace de redirección a la página de descarga de tu aplicación Android, normalmente una página de Play Store.
    withAndroidDL Pasar un enlace profundo para una página dentro de tu aplicación Android.
    withAndroidDDL Pasar un enlace profundo diferido, es decir, un enlace a una página de tu aplicación Android que el usuario aún no ha instalado.
    withIosRedirect Pasar un enlace de redirección a la página de descarga de tu aplicación para iOS, normalmente una página de la App Store.
    withIosDL Pasar un enlace profundo a una página dentro de tu aplicación iOS.
    withIosDDL Pasar un enlace profundo diferido, es decir, un enlace a una página de tu aplicación para iOS que el usuario aún no ha instalado.
  6. [Opción avanzada] Utilizar código para forzar la ocultación/visualización de banners

    Como se mencionó en el paso 3, si tiene una aplicación de una sola página, tiene que utilizar los métodos hideBanner() y showBanner() en cada ruta de página para asegurarse de que se entrega el banner adecuado (véase más arriba).

    hideBanner() Los métodos showBanner()y también están disponibles para que los utilices a lo largo de tu código si quieres ocultar un banner que de otro modo se mostraría, o volver a mostrar un banner que ocultaste.

    Método Descripción
    singularSdk.hideBanner() Ocultar un banner visible de su página.
    singularSdk.showBanner() Mostrar el banner preconfigurado.
    singularSdk.showBanner(params) Mostrar el banner preconfigurado pero sobreescribiendo los enlaces con las opciones definidas en el objeto linkParams (ver paso 4).

Propiedades globales

Propiedades globales
#

El SDK de Singular le permite definir propiedades personalizadas que se enviarán a los servidores de Singular junto con cada sesión y evento enviado desde la aplicación. Estas propiedades pueden representar cualquier información que desees sobre el usuario, el modo/estado de la app o cualquier otra cosa.

  • Puedes definir hasta 5 propiedades globales como un objeto JSON válido. Las propiedades globales se mantienen en el navegador localstorage hasta que se borra o se cambia el contexto del navegador.

  • Cada nombre y valor de propiedad puede tener hasta 200 caracteres. Si pasas un nombre o valor de propiedad más largo, se truncará a 200 caracteres.

  • Las propiedades globales se reflejan actualmente en los registros de eventos a nivel de usuario de Singular (véase Exportación de registros de atribuciones) y en los postbacks.

  • Si es necesario, las propiedades globales pueden enviarse a un tercero desde Singular con fines de comparación.

Para soportar la configuración de Propiedades Globales antes de que el WebSDK sea inicializado debe implementar la función personalizada setGlobalPropertyBeforeInit, y llamarla antes de la Inicialización del SDK. Si no necesita configurar las propiedades globales antes de la inicialización, puede omitir este código personalizado.

Para manejar las Propiedades Globales después de la inicialización, debe utilizar las funciones del SDK: setGlobalProperties(), getGlobalProperties(), clearGlobalProperties().

setGlobalPropertyBeforeInit()setGlobalProperties()getGlobalProperties()clearGlobalProperties()
/**
 * Set a Singular global property before SDK initialization.
 * Allows up to 5 key/value pairs. Optionally overwrites existing value for a key.
 * 
 * @param {string} sdkKey - The Singular SDK key.
 * @param {string} productId - The Singular product ID.
 * @param {string} propertyKey - The property key to set.
 * @param {string} propertyValue - The property value to set.
 * @param {boolean} overrideExisting - Whether to overwrite the property if it already exists.
 */
function setGlobalPropertyBeforeInit(sdkKey, productId, propertyKey, propertyValue, overrideExisting) {
  const storageKey = `${sdkKey}_${productId}_global_properties`;
  let properties = {};

  // Try to load any existing properties
  const existing = localStorage.getItem(storageKey);
  if (existing) {
    try {
      properties = JSON.parse(existing) || {};
    } catch (e) {
      // If parsing fails, reset properties
      properties = {};
    }
  }

  // Only allow up to 5 keys
  const propertyExists = Object.prototype.hasOwnProperty.call(properties, propertyKey);
  const numKeys = Object.keys(properties).length;
  if (!propertyExists && numKeys >= 5) {
    console.warn("You can only set up to 5 Singular global properties.");
    return;
  }

  // Apply logic for overwrite or skip
  if (propertyExists && !overrideExisting) {
    console.log(`Global property '${propertyKey}' exists and overrideExisting is false; property not changed.`);
    return;
  }

  properties[propertyKey] = propertyValue;
  localStorage.setItem(storageKey, JSON.stringify(properties));
  console.log("Singular Global Properties set:", storageKey, properties);
}

Seguimiento de búsquedas orgánicas

Ejemplo de seguimiento de búsqueda orgánica
#

¡IMPORTANTE! Este ejemplo se proporciona como una solución alternativa para activar el seguimiento de búsqueda orgánica. El código debe ser utilizado sólo como un ejemplo y actualizado y mantenido por el desarrollador web en función de las necesidades de su departamento de marketing. El seguimiento de Búsqueda Orgánica puede tener diferentes significados por anunciante. Por favor, revise el ejemplo y ajústelo a sus necesidades.

¿Por qué utilizarlo?

  • Asegura que las visitas de búsqueda orgánica sean rastreadas correctamente, incluso si no hay parámetros de campaña presentes.

  • Añade el parámetro Singular "Source" wpsrccon el valor del (referrer) y el parámetro "Campaign Name" wpcn como "OrganicSearch" a la URL para una atribución clara.

  • Almacena la URL actual y el referente en localStoragepara su uso posterior.

  • Puro JavaScript, sin dependencias y fácil de integrar.

Cómo funciona

  1. Comprueba la URL de la página en busca de parámetros de campaña conocidos de (Google, Facebook, TikTok, UTMs, etc.).

  2. Si no hay parámetros de campaña presentes y el referente es un motor de búsqueda, añade:

    • wpsrc (con el referente como valor)
    • wpcn (con OrganicSearch como valor)
  3. Actualiza la URL en el navegador sin recargar la página.

  4. Almacena la URL actual y el referente en localStoragecomo sng_url y sng_ref.

Uso de

  1. Añade setupOrganicSearchTracking.js a tu sitio como una librería.
  2. Llama a la función setupOrganicSearchTracking() ANTES de inicializar el WebSDK.
setupOrganicSearchTracking.js
// singular-web-organic-search-tracking: setupOrganicSearchTracking.js
// Tracks organic search referrals by appending wpsrc and wpcn to the URL if no campaign parameters exist and the referrer is a search engine.

// Configuration for debugging (set to true to enable logs)
const debug = true;

// List of campaign parameters to check for exclusion
const campaignParams = [
    'gclid', 'fbclid', 'ttclid', 'msclkid', 'twclid', 'li_fat_id',
    'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'wpsrc'
];

// Whitelist of legitimate search engine domains (prevents false positives)
const legitimateSearchEngines = new Set([
    // Google domains
    'google.com', 'google.co.uk', 'google.ca', 'google.com.au', 'google.de', 
    'google.fr', 'google.it', 'google.es', 'google.co.jp', 'google.co.kr',
    'google.com.br', 'google.com.mx', 'google.co.in', 'google.ru', 'google.com.sg',
    
    // Bing domains  
    'bing.com', 'bing.co.uk', 'bing.ca', 'bing.com.au', 'bing.de',
    
    // Yahoo domains
    'yahoo.com', 'yahoo.co.uk', 'yahoo.ca', 'yahoo.com.au', 'yahoo.de',
    'yahoo.fr', 'yahoo.it', 'yahoo.es', 'yahoo.co.jp',
    
    // Other search engines
    'baidu.com', 'duckduckgo.com', 'yandex.com', 'yandex.ru',
    'ask.com', 'aol.com', 'ecosia.org', 'startpage.com', 
    'qwant.com', 'seznam.cz', 'naver.com', 'daum.net'
]);

// Extract main domain from hostname (removes subdomains)
function getMainDomain(hostname) {
    if (!hostname) return '';
    
    const lowerHost = hostname.toLowerCase();
    
    // Handle special cases for known search engines with country codes
    const searchEnginePatterns = {
        'google': (host) => {
            // Match google.TLD patterns more precisely
            if (host.includes('google.co.') || host.includes('google.com')) {
                const parts = host.split('.');
                const googleIndex = parts.findIndex(part => part === 'google');
                if (googleIndex !== -1 && googleIndex < parts.length - 1) {
                    return parts.slice(googleIndex).join('.');
                }
            }
            return null;
        },
        'bing': (host) => {
            if (host.includes('bing.co') || host.includes('bing.com')) {
                const parts = host.split('.');
                const bingIndex = parts.findIndex(part => part === 'bing');
                if (bingIndex !== -1 && bingIndex < parts.length - 1) {
                    return parts.slice(bingIndex).join('.');
                }
            }
            return null;
        },
        'yahoo': (host) => {
            if (host.includes('yahoo.co') || host.includes('yahoo.com')) {
                const parts = host.split('.');
                const yahooIndex = parts.findIndex(part => part === 'yahoo');
                if (yahooIndex !== -1 && yahooIndex < parts.length - 1) {
                    return parts.slice(yahooIndex).join('.');
                }
            }
            return null;
        }
    };
    
    // Try specific patterns for major search engines
    for (const [engine, patternFn] of Object.entries(searchEnginePatterns)) {
        if (lowerHost.includes(engine)) {
            const result = patternFn(lowerHost);
            if (result) return result;
        }
    }
    
    // Handle other known engines with simple mapping
    const otherEngines = {
        'baidu.com': 'baidu.com',
        'duckduckgo.com': 'duckduckgo.com', 
        'yandex.ru': 'yandex.ru',
        'yandex.com': 'yandex.com',
        'ask.com': 'ask.com',
        'aol.com': 'aol.com',
        'ecosia.org': 'ecosia.org',
        'startpage.com': 'startpage.com',
        'qwant.com': 'qwant.com',
        'seznam.cz': 'seznam.cz',
        'naver.com': 'naver.com',
        'daum.net': 'daum.net'
    };
    
    for (const [domain, result] of Object.entries(otherEngines)) {
        if (lowerHost.includes(domain)) {
            return result;
        }
    }
    
    // Fallback: Extract main domain by taking last 2 parts (for unknown domains)
    const parts = hostname.split('.');
    if (parts.length >= 2) {
        return parts[parts.length - 2] + '.' + parts[parts.length - 1];
    }
    
    return hostname;
}

// Get query parameter by name, using URL.searchParams with regex fallback for IE11
function getParameterByName(name, url) {
    if (!url) url = window.location.href;
    try {
        return new URL(url).searchParams.get(name) || null;
    } catch (e) {
        if (debug) console.warn('URL API not supported, falling back to regex:', e);
        name = name.replace(/[\[\]]/g, '\\$&');
        const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
        const results = regex.exec(url);
        if (!results) return null;
        if (!results[2]) return '';
        return decodeURIComponent(results[2].replace(/\+/g, ' '));
    }
}

// Check if any campaign parameters exist in the URL
function hasAnyParameter(url, params) {
    return params.some(param => getParameterByName(param, url) !== null);
}

// Improved search engine detection - only checks hostname, uses whitelist
function isSearchEngineReferrer(referrer) {
    if (!referrer) return false;
    
    let hostname = '';
    try {
        hostname = new URL(referrer).hostname.toLowerCase();
    } catch (e) {
        // Fallback regex for hostname extraction
        const match = referrer.match(/^(?:https?:\/\/)?([^\/\?#]+)/i);
        hostname = match ? match[1].toLowerCase() : '';
    }
    
    if (!hostname) return false;
    
    // First check: exact match against whitelist
    if (legitimateSearchEngines.has(hostname)) {
        if (debug) console.log('Exact match found for:', hostname);
        return true;
    }
    
    // Second check: subdomain of legitimate search engine
    const hostParts = hostname.split('.');
    if (hostParts.length >= 3) {
        // Try domain.tld combination (e.g., google.com from www.google.com)
        const mainDomain = hostParts[hostParts.length - 2] + '.' + hostParts[hostParts.length - 1];
        if (legitimateSearchEngines.has(mainDomain)) {
            if (debug) console.log('Subdomain match found for:', hostname, '-> main domain:', mainDomain);
            return true;
        }
        
        // Try last 3 parts for country codes (e.g., google.co.uk from www.google.co.uk)
        if (hostParts.length >= 3) {
            const ccDomain = hostParts[hostParts.length - 3] + '.' + hostParts[hostParts.length - 2] + '.' + hostParts[hostParts.length - 1];
            if (legitimateSearchEngines.has(ccDomain)) {
                if (debug) console.log('Country code domain match found for:', hostname, '-> cc domain:', ccDomain);
                return true;
            }
        }
    }
    
    if (debug) {
        console.log('Hostname not recognized as legitimate search engine:', hostname);
    }
    
    return false;
}

// Main function to update URL with organic search tracking parameters
function setupOrganicSearchTracking() {
    const url = window.location.href;
    const referrer = document.referrer || '';

    // Store URL and referrer in localStorage
    try {
        localStorage.setItem('sng_url', url);
        localStorage.setItem('sng_ref', referrer);
    } catch (e) {
        if (debug) console.warn('localStorage not available:', e);
    }

    if (debug) {
        console.log('Current URL:', url);
        console.log('Referrer:', referrer);
    }

    // Skip if campaign parameters exist or referrer is not a search engine
    const hasCampaignParams = hasAnyParameter(url, campaignParams);
    if (hasCampaignParams || !isSearchEngineReferrer(referrer)) {
        if (debug) console.log('Skipping URL update: Campaign params exist or referrer is not a legitimate search engine');
        return;
    }

    // Extract and validate referrer hostname
    let referrerHostname = '';
    try {
        referrerHostname = new URL(referrer).hostname;
    } catch (e) {
        if (debug) console.warn('Invalid referrer URL, falling back to regex:', e);
        referrerHostname = referrer.match(/^(?:https?:\/\/)?([^\/]+)/i)?.[1] || '';
    }

    // Extract main domain from hostname
    const mainDomain = getMainDomain(referrerHostname);
    
    if (debug) {
        console.log('Full hostname:', referrerHostname);
        console.log('Main domain:', mainDomain);
    }

    // Only proceed if main domain is valid and contains safe characters
    if (!mainDomain || !/^[a-zA-Z0-9.-]+$/.test(mainDomain)) {
        if (debug) console.log('Skipping URL update: Invalid or unsafe main domain');
        return;
    }

    // Update URL with wpsrc and wpcn parameters
    const urlObj = new URL(url);
    
    // Set wpsrc to the main domain (e.g., google.com instead of tagassistant.google.com)
    urlObj.searchParams.set('wpsrc', mainDomain);
    
    // Set wpcn to 'Organic Search' to identify the campaign type
    urlObj.searchParams.set('wpcn', 'Organic Search');

    // Update the URL without reloading (check if history API is available)
    if (window.history && window.history.replaceState) {
        try {
            window.history.replaceState({}, '', urlObj.toString());
            if (debug) console.log('Updated URL with organic search tracking:', urlObj.toString());
        } catch (e) {
            if (debug) console.warn('Failed to update URL:', e);
        }
    } else {
        if (debug) console.warn('History API not supported, cannot update URL');
    }
}

Seguimiento entre subdominios

Por defecto, el Singular WebSDK genera un Singular Device ID y lo persiste utilizando el almacenamiento del navegador. Dado que este almacenamiento no puede compartirse entre subdominios, el SDK acaba generando un nuevo ID para cada subdominio.

Si desea conservar el ID de dispositivo singular en todos los subdominios, puede utilizar una de las siguientes opciones:

Método B (Avanzado): Configurar manualmente el ID de dispositivo singular
#

Método B (Avanzado): Establecimiento manual del ID de dispositivo singular

Si no desea que Singular SDK mantenga el ID de dispositivo automáticamente, puede mantener el ID manualmente en todos los dominios, por ejemplo, utilizando una cookie de dominio de nivel superior o una cookie de servidor. El valor debe ser un ID generado previamente por Singular, en formato uuid4 válido.

NOTA: Puede leer el ID del dispositivo Singular utilizando singularSdk.getSingularDeviceId() después de llamar al método init o utilizando InitFinishedCallback.

Método withPersistentSingularDeviceId

Descripción

Inicializa el SDK con las opciones de configuración, incluido el Id. de dispositivo singular que se desea mantener.

Firma withPersistentSingularDeviceId(singularDeviceId)
Ejemplo de uso
function initSingularSDK() {
  const config = new SingularConfig(sdkKey, sdkSecret, productId)
    .withPersistentSingularDeviceId(singularDeviceId);
  window.singularSdk.init(config);
}

Pasos siguientes

Artículos relacionados