Sending Engagements on Web


  • Promoted uses the Snowplow web client for sending records from web browsers to your web proxy.
  • Your web proxy forwards the Snowplow message to our Snowplow-compatible Metric API endpoint.

1. Add Snowplow tracking tag to your pages.

Promoted uses the Snowplow client because it has built-in support for batching, retries and privacy settings.

Here's an example that initializes the Snowplow client on an HTML page.

const isProduction = false; 
const loggingApiId = isProduction ? 'yourmarket-prod' : ‘yourmarket-dev';
const cookieSecure = isProduction;
const cookieSameSite = isProduction ? 'None' : 'Lax';
<script type="text/javascript">
    window.snowplow('newTracker', 'cf', + '/api/logger', {
      appId: '${loggingApiId}',
      cookieDomain: window.location.hostname,
      cookieSecure: ${cookieSecure},
      cookieSameSite: '${cookieSameSite}',
      contexts: {
        webPage: true,
        clientHints: true
      maxLocalStorageQueueSize: 100
    // Some other optional settings mentioned lower in this document.

This script block is fast and will asynchronously load the Snowplow JS Tracker and queue log records until Snowplow JS Tracker is ready. If you want that script block to be executed asynchronously too, the promoted-snowplow-logger supports queuing log calls until window.snowplow is set.

2. Add the minified Snowplow Tracker to your public static resources

Add the Javascript to your static serving serving system. E.g. a CDN or a public/ path in your web server.

We’ve tested with Snowplow v2.16.3. Here is a direct link to download. (79KB).

3. Create a web proxy endpoint that forwards the Snowplow HTTP requests to Promoted’s Metrics Snowplow API

We recommend platforms host a web endpoint that forwards the Snowplow requests to Promoted’s servers. We recommend the marketplace do this forwarding so (1) you can authenticate/validate events and (2) reduces the chance of the endpoint getting blocked by future browser extensions.

Here is an example using NextJS.

import axios from 'axios';
const snowplowEndpoint = ''; // From env or config.
const apiKey = 'PROMOTED_PROD_METRICS_API_KEY'; // From secret store.
export default async (req, res) => {
  // TODO - authenticate the events are from this user.
  // TODO - prevent Requests and Insertions from being logged to this endpoint.
  const logResponse = await
      sp: req.body,
      ua: req.headers['user-agent'],
      headers: {
        "x-api-key": apiKey,
      timeout: 5000,
  res.statusCode = logResponse.status
  res.json({ status: logResponse.statusText })

If creating a proxy is a challenge, please sync with the Promoted team. This is easy to do with Nodejs and NextJS servers. If required, it's also possible to log to Promoted by having registering a first party DNS record that sends events to Promoted or use Promoted's URLs directly.

4. Create an instance of our EventLogger

Here is an example using client side code.


import axios from 'axios';
import { createEventLogger } from 'promoted-snowplow-logger';
export const promotedLoggingEnabled = false;
// Throw when developing.
const throwError =
  (typeof window?.location !== "undefined" && window?.location?.hostname === "localhost");
export const handleError = throwError ? (err) => { throw err; } : console.error;
export const eventLogger = createEventLogger({
  enabled: promotedLoggingEnabled,
  platformName: 'yourmarket',

5. Log Impression and Actions

  • ImpressionImpression - When an item is viewed on a screen long enough to count as an impression. E.g. >50% visible for >1s.s = When an item is in the viewport long enough to be considered viewed by a user.
  • <<glossary:Action>s = When a user acts on something (e.g. clicks on item, links, like, purchase, email sign up, expand into details, etc.).

For web, we have a small React hook / HOC to help log impressions. It uses an Intersection Observer to get notifications about when an item is viewed long enough.

import { useImpressionTracker } from 'impression-tracker-react-hook';
import { eventLogger, handleError } from '../../utils/logutils;
// React Component.
function Product(props) {
  const [impressionRef, impressionId, logImpressionFunctor] = useImpressionTracker({
    insertionId: props.insertionId,
    logImpression: eventLogger.logImpression.bind(eventLogger),
  return <div ref={impressionRef} onClick={() => {
    // In case the item was clicked too quickly, log an impression.
      targetUrl: ‘...’,
      elementId: ‘...’,

Optional - Automatic Link Tracking

The Snowplow tracker has a way of automatically tracking all links. Here is a code snippet to add to the script tag to enable this.

<script type="text/javascript">
    // Optional - enables tracking of links.  Also looks impressionId on <a> tags.
    // This allows us to attribute clicks to impressions.
    window.snowplow('enableLinkClickTracking', null, null, true,
      [(element) => {
        // Construct the context.
        const impressionId = element?.attributes?.getNamedItem("impressionid")?.value;
          if (impressionId) {
            return {
              schema: 'iglu:ai.promoted/impression_cx/jsonschema/1-0-0',
              data: {
          } else {
            return undefined;
    // Optional - automatic update tracking links when the dom changes.
    // Delay attach a MutationObserver to update automatic link tracker.
    const setupRefresh = () => {
      if (MutationObserver) {
        var observer = new MutationObserver(() => {
        var target = document.querySelector('body');
          if (!target) {
            // Delay if body is not ready.
            window.setTimeout(setupRefresh, 250);
          observer.observe(target, {
            subtree: true,
            childList: true
    // Delay setup until after page load.
    window.setTimeout(setupRefresh, 250);

Anchor tags that are automatically tracked won’t have impressionIds on them automatically. You will need to add an attribute that enableLinkClickTracking context function resolves.

  return <div ref={impressionRef}>
    <a href=’blah’ impressionId={impressionId}>

Optional - Set up and log (Page) Views

Logging (page, screen or activity) Views are not required initially. Promoted does not currently use Views when ranking content.

Promoted might ask you to log Views to:

  • Help with debugging.
  • Help us improve the quality of joins if you issue multiple Requests on the same page.
import { getViewContexts } from 'promoted-event-logger-ts';
<script type="text/javascript">
  ... // after Snowplow client initialization.

  // This takes a View object.
  const pageViewContexts = getViewContexts({
  // Optional.  For Single Page Apps, you can move this to another route/mount. 
  window.snowplow('trackPageView', null, ${JSON.stringify(pageViewContexts)});

If you have a Single Page Application, you can also move the trackPageView call to a route that indicates a different page.

Did this page help you?