1.1 Fit Finder Widget

The Fit Finder integration produces two scripts:

  • The pdp.js script, which will be added to (all) Product Detail Pages (PDP). This script serves to:

    • Install the Fit Finder button, through which the user can open the Fit Finder size advisor, on the PDP.
    • Collect and send all the necessary data from the shop to the Fit Finder database to provide a recommendation.
  • The orderconfirmation.js script, which will be added to the checkout / Order Confirmation Page (OCP). This script serves at the end of the user journey to:

    • Connect items with a size recommendation and the purchased item.
    • Help improve fit prediction models and the size recommendation engine.
    • Collect purchase data for Fit Insights reports.

Neither of the scripts collects or stores any personal identifiable information on a user.

1.2 Integration Data Objects

Fit Finder relies on various elements of a shop’s website, which means the integration can be impacted by any changes made to these dependencies. Changes to a shop’s infrastructure or design can be expected over time but might cause Fit Finder to no longer load correctly.

Therefore Fit Finder integrations are built in a way to prevent these outages, resulting from changes to the dependency elements. To do our best to ensure a stable integration we recommend providing data objects on the PDP (Product Detail Page) and OCP (Order Confirmation Page). If your integration includes FitFinder on PLP (Product Listing Page) please provide a data object there as well. Your technical resources will be responsible for implementing these objects.

Considerations to take into account:

  • The PDP object should update whenever the user changes the color or navigates to a different product.
  • The objects should be placed directly on the page and not via tag manager (e.g. Google Tag Manager).
  • The size values in the objects should match the values in the product and return feeds.
  • All functions should be implemented by you in your preferred method as long as they provide the expected values and behaviour. Some functions are only needed in case of additional functionalities and features so please refer to the "Description" column in the table below for the specific use cases.
  • experimentFunnel is reported in case of an external A/B test. You’re free to select the value as you see fit (it should identify a given funnel used on the page).
  • For an explanation of shop session ID please look at a section of the page dedicated to that.
  • All properties are required unless marked with a question mark following the property name. There are some special cases, which are flagged in the property description in the table below.


Product Detail Page (PDP) Object

+
type fitAnalyticsData = {
    shopLanguage: string;
    shopCountry: string;
    mainImageLink: string;
    user: {
        id: string | null;
        hasLoggedIn: boolean;
    }
    currentItemSubgroupId: string;
    allItemSubgroupIds: string[]
    shopSessionId: string;
    experimentFunnel?: string;
    sizes: {
        value: string;
        isAvailable: boolean;
    }[]
    consent: {
        hasAnalyticsConsent(): Promise | boolean;
    }
    operations: {
        getAddToCartBtn(): HTMLElement;
        getSizeChartLink(): HTMLElement;
        addToCart(itemSubgroupId: string, size: string): void;
        selectSize(itemSubgroupId: string, size: string): void;
        getCurrentSize(): string;
        getCartItems(): Promise<Array<{itemSubgroupId: string; size: string}>> | Array<{itemSubgroupId: string, size: string}>;
    }
}
Property Description
shopLanguage ISO 639-1, e.g. ‘en’
shopCountry ISO 3166-1, e.g. ‘GB’
mainImageLink URL to a product picture that will be displayed within the widget. Reminder: the value should be updated in case a visitor selects a different color.
user.id In case that a user is logged in, it should be a unique user identifier. It shouldn’t depend on the session and it should be consistent and have the same value in both data objects. In case of visitors that aren’t logged in it should be null.
user.hasLoggedIn It should be true in case the user is logged in, false otherwise.
currentItemSubGroupId The current item subgroup ID is a color-level identifier for the product the user is currently looking at.
allItemSubgroupIds Contains all item subgroup IDs for different colors of the product (and because of that it should contain the currentItemSubgroupId value).
shopSessionId See the specifications in the dedicated section below.
sizes Is an array of objects, which correspond to the different sizes on the PDP. Each size object should have two properties: the size value and the value of the availability. The size value should be identical to the one in the feed. In case of 2D sizes (e.g. width and length) we need all combinations e.g. “40 long” (that again match the format as the sizes in the feed).
consent.hasAnalyticsConsent This function should return the explicit consent state given by the user after interacting with your cookie consent banner. You should return true if the visitor has either clicked on "Accept All" or specifically clicked on the option that allows FitFinder to track session identifiers. You should return false if the visitor has either closed the consent banner, clicked on "Reject All" or specifically clicked on the option that rejects FitFinder to track session identifiers.
operations.getAddToCartBtn This function should return the HTML element of the add to cart button.
operations.getSizeChartLink This function should return the HTML element of the size chart button.
operations.addToCart This function should add a given size of a given product to cart.
operations.selectSize This function should select a given size for a given product on the page.
operations.getCurrentSize This function should return the currently selected size. Required in case we should provide our multiple size alert (MSA) feature, otherwise optional.
operations.getCartItems This function should return the products currently in cart. Required in case we should provide our multiple size alert (MSA) feature, otherwise optional.

Example object
fitAnalyticsData = {
    shopLanguage: "de",
    shopCountry: "DE",
    mainImageLink: "https://example.com/de_de/example-product.jpg",
    user: {
        id: "qvfu93nh9q8ufv389qnh3quw3u"
        hasLoggedIn: true
    },
    currentItemSubgroupId: "19603829",
    allItemSubgroupIds: ["19603828", "19603827", "19603829", "19603821"],
    shopSessionId: "aa9a93gbhna93hna9hnsi9aahn",
    experimentFunnel: "control",
    sizes: [
        { value: "42", isAvailable: true },
        { value: "44", isAvailable: false },
        { value: "46", isAvailable: false }
    ],
    consent: {
        hasAnalyticsConsent() {...}
    },
    operations: {
        getAddToCartBtn: function () {...},
        getSizeChartLink: function () {...},
        addToCart: function (currentItemSubgroupId, size) {...},
        selectSize: function (currentItemSubgroupId, size) {...},
        getCurrentSize: function () {...},
        getCartItems: function () {...}
    }
}


Order Confirmation Page (OCP) Object

+
type fitAnalyticsData = {
    orderId: string;
    shopLanguage: string;
    shopCountry: string;
    currency: string;
    user: {
        id: string | null;
        hasLoggedIn: boolean;
    }
    shopSessionId: string;
    consent: {
        hasAnalyticsConsent(): Promise | boolean;
    }
    experimentFunnel?: string;
    products: {
        size: string;
        itemId: string;
        itemSubgroupId: string;
        price: number | string;
        quantity: number | string;
        gtin: string;
    }[]
}
Property Description
orderId The order ID of the purchase, should match what’s displayed on the purchase success page.
shopLanguage ISO 639-1, e.g. ‘en’
shopCountry ISO 3166-1, e.g. ‘GB’
currency The currency that the visitor paid in, should be ISO 4217, e.g. 'EUR’.
user.id In case that a user is logged in, it should be a unique user identifier. It shouldn’t depend on the session and it should be consistent and have the same value in both data objects. In case of visitors that aren’t logged in it should be null.
user.hasLoggedIn It should be true in case the user is logged in, false otherwise.
shopSessionId See the specifications in the dedicated section below.
consent.hasAnalyticsConsent This function should return the explicit consent state given by the user after interacting with your cookie consent banner. You should return true if the visitor has either clicked on "Accept All" or specifically clicked on the option that allows FitFinder to track session identifiers. You should return false if the visitor has either closed the consent banner, clicked on "Reject All" or specifically clicked on the option that rejects FitFinder to track session identifiers.
product.size The size of the product that the user purchased, this value should always match the value on the PDP and in the feed.
product.itemId A size-level identifier of a product. This value should match the item_id (or equivalent) in the returns feed and the id (or equivalent) field in the product feed.
product.itemSubgroupId A color-level identifier. It needs to match the value from the PDP data object and the product feed column that contains these identifiers.
product.price The unit price that the user paid to purchase the product (including taxes and discounts).
product.quantity The quantity that the user purchased of a specific product.
product.gtin A unique product identifier, usually 13 digits long. https://en.wikipedia.org/wiki/Global_Trade_Item_Number

Example object
fitAnalyticsData = {
    orderId: "127591",
    shopLanguage: "de",
    shopCountry: "DE",
    currency: "USD",
    user: {
        id: "qvfu93nh9q8ufv389qnh3quw3u",
        hasLoggedIn: true
    }
    shopSessionId: "aa9a93gbhna93hna9hnsi9aahn",
    consent: {
        hasAnalyticsConsent() {...}
    },
    products: [
        {
            size: "42",
            itemId: "1960382803",
            itemSubgroupId: "19603828",
            price: 27.99,
            quantity: 2,
            gtin: "5714559416776"
        },
        {
            size: "46",
            itemId: "1960382705",
            itemSubgroupId: "19603827",
            price: 48.99,
            quantity: 1,
            gtin: "8255183046278"
        },
        //... the same per each purchased product
    ]
}


Product Listing Page (PLP) Object

+
type fitAnalyticsData = {
    shopLanguage: string;
    shopCountry: string;
    user: {
        id: string | null;
        hasLoggedIn: boolean;
    }
    shopSessionId: string;
    consent: {
        hasAnalyticsConsent(): Promise | boolean;
    };
}
Property Description
shopLanguage ISO 639-1, e.g. ‘en’
shopCountry ISO 3166-1, e.g. ‘GB’
user.id In case that a user is logged in, it should be a unique user identifier. It shouldn’t depend on the session and it should be consistent and have the same value in both data objects. In case of visitors that aren’t logged in it should be null.
user.hasLoggedIn It should be true in case the user is logged in, false otherwise.
shopSessionId See the specifications in the dedicated section below.
consent.hasAnalyticsConsent This function should return the explicit consent state given by the user after interacting with your cookie consent banner. You should return true if the visitor has either clicked on "Accept All" or specifically clicked on the option that allows FitFinder to track session identifiers. You should return false if the visitor has either closed the consent banner, clicked on "Reject All" or specifically clicked on the option that rejects FitFinder to track session identifiers.

Example object
fitAnalyticsData = {
    shopLanguage: "de",
    shopCountry: "DE",
    user: {
        id: "qvfu93nh9q8ufv389qnh3quw3u"
        hasLoggedIn: true
    },
    shopSessionId: "aa9a93gbhna93hna9hnsi9aahn",
    consent: {
        hasAnalyticsConsent() {...}
    }
}


1.3 Data Object Validator

The Fit Finder data object validator is a way to test and validate that the data objects, being implemented by the shop’s technical resources, is as expected, all mandatory properties are there, property values are in the correct type and format, and operations are working properly.

The solution we have now is a simple js script that when run on a PDP with data object, it reads the object, validates the data and logs the validation results on the browser’s console.

How to use the script:

  • Calling the links:

The script can be called externally on a PDP / OCP with the data object provided. https://osiris.fitanalytics.com/pdp-checker.osiris.js

  • Using a JS injector:

A simple JS injector can be used to inject the script into the PDP / OCP with the data object provided.

The data object validation results can then be seen by opening the devTools' console tab.

1.4 Immediate Recommendation

Once a user has completed the recommendation flow and received a size recommendation, Fit Finder delivers size recommendations on the following Product Detail Pages (PDPs). This feature allows Fit Finder users to obtain recommendations immediately during the shopping journey or upon a user’s return to any supported PDP on the shop. The Fit Finder call to action (CTA) button will change to the immediate recommendation text for returning users and they will not have to go through the flow again.



Fit Finder Click To Action Text

Fit Finder Immediate Recommendation Text

1.5 Order Confirmation Page

Fit Finder integration produces two scripts, one script that runs on the product detail page and enables the visual front end functionality of Fit Finder and one script that runs on the checkout/order confirmation page. The orderconfirmation.js script, serves at the end of the user journey to connect purchases, track the user journey from size recommendation to purchase and helps improve fit prediction models.

1.6 Customization

Parts of the Fit Finder experience can be customized to support a shop’s visual brand guidelines. Within Fit Finder standard integration these following elements are customizable:

Shop Logo:

dimensions of the logo placeholder are W: 200 px and H: 60px max or higher resolution.limit: Logo should not be bigger than 10 MB.Format: The best option is SVG, however we can also work with JPG and PNG. If a shop can’t provide one of the latter formats, the resolution of the image should be at least 300ppi.

Button Color:

(CTA) button, Immediate recommendation (IR) button, Add-to-Cart (A2C) button and feature tool tips, can be customized in a brand color.brand colors must be specified as a six-digit hexadecimal valueCTA button and tool tips, different brand color can be used, a primary HEX and secondary HEXmost cases the Primary HEX should be dark enough for the text upon it to be white as it is here:

Classic Fit Finder Designs:

Custom Color Design:

Customizable Tool Tips:

1.7 How does widget.js work

To interact with Fit Finder, the widget needs to collect certain information, such as the shop country, the language, the color-id of the selected product. The Fit Finder script fetches this information on the shop and will also install the button. The widget’s size is written in native coffeescript without relying on frameworks for various reasons:

  • the widget’s size is minimal
  • the widget is cross platform and works with different client shops
  • we have full control over widget functionalities

After collecting the necessary data and interacting with a user, Fit Finder recommends a size to the user, and user feed data is stored in the widget. In this process no personal identifiable data of any user is used or stored.

1.8 Compatibility

Fit Finder’s current code base offers support for a wide range of legacy browsers and devices. It can operate without jQuery or alternatively, load jQuery 1.8.3 in noConflict mode, unless there is already a jQuery library with version 1.7 or newer present. To avoid version conflicts with different jQuery libraries, please make sure to run any critical code that relies on older jQuery versions before loading the Fit Finder widget code.

1.9 Adding Fit Finder to your Page - Technical impact

Fit Finder is a third-party, asynchronously loaded script. For that reason, our scripts are deprioritized in the browser queuing system, causing delays until API requests are made to our servers. This queueing logic is subject to specific browser technology and is beyond our control.

We rely on certain data points on the PDP that are populated late in the page load process. Therefore, it is a natural choice to let the browser initiate the Fit Finder loading only when that information becomes available.

The alternative - to somehow forcefully make the Fit Finder scripts load earlier in the page load process - would come at the expense of delayed loading of first-party assets on the page, which would risk having an adverse effect on SEO scores.

Once our scripts are ready, they fire API requests to our backend in order to fetch configuration and check if the product on the PDP is supported. These API requests adhere to our contractually agreed upon SLAs as measured in our external load balancer in the Google Cloud data center in Belgium (europe-west1-d). Our SLAs are the same year round, so we do not expect increased response times during e.g. peak or holiday periods.

Alongside our SLAs for API requests, our widget assets comply with small size requirements to prevent delays in eCommerce sites (~=100kB).

1.10 Pipeline of network requests

Fit Finder starts loading when the browser loads our lightweight integration script, e.g. https://integrations.fitanalytics.com/shop/[your shop code]/pdp.js (2.1kB).

After its load, it will internally set up an integration object and load a second external script that contains our widget codebase from https://widget.fitanalytics.com/widget_pdp.js. This second script will kickstart its load just after the DOM is in a ready state (loaded).

When the widget_pdp.js loads, it will make a few API calls to our endpoints:

  1. GET https://widget.fitanalytics.com/widget/api/shops/[your shop code] - Validates user consent requirements in certain markets.
  2. GET https://widget.fitanalytics.com/widget/api/shops/[your shop code] - Same URL as the first call but called with a different set of input parameters. Retrieves an internal shop object containing the widget's configuration.
  3. GET https://widget.fitanalytics.com/widget/api/products - Validates the product eligibility against our system.

Note that between each request, the browser may enqueue any subsequent requests and deprioritize them in the internal queue as we are a third party library and the requests go to a third party domain.

After the third call, we render the button on the page if the product is supported. More API calls will follow but at this point we have done everything we can to display the Fit Finder button as quickly as possible.

1.11 Frequently asked questions

If the Javascript bundles fail to load or the initial API requests fail, will this break the PDP?

No. The Fit Finder button will simply not be displayed.

Are there fallback options if the network is slow or unreliable?

We currently have retry and fallback logic on the collection of widget interaction events. Other API calls (to fetch product metadata or configuration, widget interactions, or size recommendations) currently do not have retries or fallbacks but do have a timeout, after which an error message will be displayed in the widget if it is visible on screen.

What is the expected response time for static assets?

Non-interactive static content such as *.js files and images are served through CDNs to ensure short response times and high throughput independent of the user's location.

What is the expected response time for widget interactions?

This is subject to the contractual SLAs as measured from our external load balancer in Belgium. You have to account for network round trip latency depending on the end user’s geographical location. An end user in New York City would typically see at least additional 120 ms latency on top of the request fulfillment time inside our data center. For example, if our backend system needs 100 ms to fulfill a size recommendation request, a New York City end user would experience around 220 ms of latency.

2.2 Shop Session ID

Due to updated security policies on iOS devices, it is often not possible for Fit Finder scripts to interact with 3rd party cookies, such as connect.sid. This is a growing issue, as our data shows that more users are viewing Fit Finder on iOS devices. Lack of session ID’s lead to issues tracking the user journey between PDP and OCP. A solution for this problem is to retrieve a first party (generated shop side) session ID. In the integration object, it’s called shop session ID. The primary location for such an ID is a first party cookie, native to the shop’s site. The Session ID must meet the following criteria and should only be shared with us when users have consented to analytics cookies:

  • It should be unique per visitor and preserved between visits from the same browser for the duration of their session.
    • Its value must stay the same when the visitor logs in or out.
    • It needs to be accessible on both PDP and OCP and it must have the same value for a single visitor on both pages
    • It should be safe from misuse in case. For example it shouldn't be possible to impersonate and/or personally identify the visitor in any way with just this identifier.
    • It could be theoretically derived from the shop's internal session ID, however then it should be definitely protected (e.g. hashed with a secure hash function like SHA256), to prevent abuse as described.
    • It must be readable by JavaScript. This means cookies with the httpOnly flag are not suited for this purpose.
    • The Session ID must not contain any personally identifiable information (PII).

Questions?

We're happy to help. Send your question to support@fitanalytics.com.