CMH setup for websites

This article explains how to set up Coveo Merchandising Hub (CMH) on your site for clients working in the ecommerce vertical.

Browser compatibility

Before you start setting up CMH on your website, make sure that you’re using the latest version of the Chrome browser as this is the only browser supported by our application. For more information, see Browser compatibility.

Step 1: Add CMH scripts

To log user events and allow CMH to work efficiently, you need to include the smartserve.js script on your site.

It’s also recommended that you include the UV API script to make the implementation of CMH easier.

smartserve.js

If you have not yet implemented Smartserve on your site, it’s recommended that you do so with an asynchronous setup. As a best practice, load the script as high up in the head of the page as possible:

<script src='//static.goqubit.com/smartserve-<YOUR_PROPERTY_ID>.js' async></script>
Note

The Content API requires contextual information to be passed to it, including details about the page, product, and the basket. If you need to hit the endpoint directly, you must send the contextual information in the request.

Note

When using qubit/placement-api package in an experience, CMH abstracts this away by populating the Atom Content API request’s payload with the appropriate values from QProtocol.

UV API

CMH uses data generated by the events emitted on your website. To pass events to your website, you need to use The Universal Variable (UV) API script in your website. The UV API is s a global event emitter used to pass underlying page or view information to third-party scripts. Using the UV API script, all data is exposed as events using window.uv. All events must conform to schemas defined in the QProtocol schemas.

Note

The UV API script can only be used in IE8+, Firefox, Opera, Chrome, and Safari.

Setting up and using the UV API

The UV API should be included as an inline script on the webpage at the top of the <head> tag, immediately after the <meta charset…​ /> tag. By embedding the API in the <head> synchronously, any script in the page is able to emit or handle events without polling or waiting for asynchronous scripts to load.

The following piece of code shows how to import the script:

<script>
!function(){function n(){function n(n){p.level=n}function e(n,e){p.info(n,"event emitted"),e=c(e||{}),e.meta=e.meta||{},e.meta.type=n,u.push(e),r(),v.listeners=f(v.listeners,function(n){return!n.disposed})}function o(n,e,o){function r(){return p.info("Replaying events"),t(function(){s(v.events,function(t){c.disposed||l(n,t.meta.type)&&e.call(o,t)})}),f}function i(){return p.info("Disposing event handler"),c.disposed=!0,f}p.info("Attaching event handler for",n);var c={type:n,callback:e,disposed:!1,context:o||window};v.listeners.push(c);var f={replay:r,dispose:i};return f}function t(n){p.info("Calling event handlers"),a++;try{n()}catch(n){p.error("UV API Error",n.stack)}a--,r()}function r(){if(0===u.length&&p.info("No more events to process"),u.length>0&&a>0&&p.info("Event will be processed later"),u.length>0&&0===a){p.info("Processing event");var n=u.shift();v.events.push(n),t(function(){s(v.listeners,function(e){if(!e.disposed&&l(e.type,n.meta.type))try{e.callback.call(e.context,n)}catch(n){p.error("Error emitting UV event",n.stack)}})})}}function i(n,e,o){var t=v.on(n,function(){e.apply(o||window,arguments),t.dispose()});return t}function s(n,e){for(var o=n.length,t=0;t<o;t++)e(n[t],t)}function c(n){var e={};for(var o in n)n.hasOwnProperty(o)&&(e[o]=n[o]);return e}function l(n,e){return"string"==typeof n?n===e:n.test(e)}function f(n,e){for(var o=n.length,t=[],r=0;r<o;r++)e(n[r])&&t.push(n[r]);return t}var u=[],a=0,p={info:function(){p.level>n.INFO||console&&console.info&&console.info.apply(console,arguments)},error:function(){p.level>n.ERROR||console&&console.error&&console.error.apply(console,arguments)}};n.ALL=0,n.INFO=1,n.ERROR=2,n.OFF=3,n(n.ERROR);var v={on:o,emit:e,once:i,events:[],listeners:[],logLevel:n};return v}"object"==typeof module&&module.exports?module.exports=n:window&&void 0===window.uv&&(window.uv=n())}();
</script>

Step 2: Emit events

By embedding the API in the head synchronously, any script in the page is able to emit or handle events without polling or waiting for asynchronous scripts to load. See Coveo Experimentation Hub scripts for more information.

To emit events, call the uv.emit method on the UV API script. The events sent with this script are known as QProtocol events. See ecommerce – QProtocol events for a complete list of the ecommerce events that are defined by this protocol.

The following example code shows how to send the ecView and ecProduct events:

Example
<script>
    uv.emit('ecView', { 1
      type: 'product'
    })

    uv.emit('ecProduct', { 2
      eventType: 'detail',
      product: {
        productId: '1209012233',
        name: 'Stainless steel and leather watch'
      }
    })
</script>
1 Specifies the type of page that is being viewed as product and emits an ecView event.
2 Sends an ecProduct event to report the product that is loaded on a page and highlights the details of the product being sent.
Note

Before sending an event to the backend, smartserve.js retrieves context data stored in cookies and meta data such as the URL of the page.

This data will be available to query.

Warning

Any CMH events emitted before the first ecView event are considered invalid. Web and mobile applications emit CMH view events whenever a view is rendered.

The emitted event will have meta attached.

{
  "meta": {
    "type": "ecProduct"
  },
  "product": {
    "id": "112-334-a",
    "price": 6.99,
    "name": "18th Birthday Baloon",
    "category": ["Party Accessories", "Birthday Parties"]
  },
  "color": "red",
  "stock": 6,
  "eventType": "detail"
}

on

uv.on(type, handler, [context])

This method attaches an event handler to be called when a certain event type is emitted. The handler will be passed the event data and will be bound to the context object, if one is passed. If a regex is passed, the handler will execute on events that match the regex.

uv.on('ecProduct', function (data) {
  console.log(data)
})
// => logs data when an `ecProduct` event is emitted

const subscription = uv.on(/.*/, function (data) {
  console.log(data)
})
// => logs data for all events

The on method returns a subscription object which can detach the handler using the dispose method and can also be used to replay events currently in the event array.

Note that subscriptions that have been disposed will not call the handler when replay is called. Let’s look at an example:

subscription.dispose()
// => detatches the event handler

subscription.replay()
// => calls the handler for all events currently in uv.events

once

uv.once(type, handler, [context])

This method attaches an event handler that will be called once, on the next event emitted that matches the type specified. The handler will be passed the event data and will be bound to the context object, if one is passed.

It returns a subscription object which can detach the handler using the dispose method. If a regex is passed, the handler will execute on the next event to match the regex. Let’s look at an example:

uv.once('ecProduct', function (data) {
  console.log(data)
})
emit('ecProduct')
// => logs data

emit('ecProduct')
// => does not log

Events

The events array is a cache of events emitted since the last page load. By iterating over the array it is possible to interpret the user journey or the current state of the page.

NPM module

The uv-api is available on the NPM registry.

npm i uv-api --save

The module exports a createUv function if required using CommonJS.

const createUv = require('uv-api')
const uv = createUv()
uv.emit('ecView')

The fields given in the table below are required fields. Additional bonus fields can be sent as specified in the reference documentation.

Click expand/collapse to see the corresponding tables.

Basic audiences

expand/collapse

Required mostly for audience targeting.

Event Field (JS Data Type) Description Conditions

ecUser

user.id (String)

A unique user ID. Can be a hash of the username and/or email address

Logged in: checks for the presence or absence of a user ID Custom list (User ID): matches the user’s ID to an uploaded list

user.email (String)

The user’s primary email address

Custom list (Email list): matches the user’s email to an uploaded list

user.loyalty.membershipType

The user’s loyalty membership type, if available

Membership type: matches the user’s value

ecProduct (PDP)

product.productId (String)

Unique identifier to distinguish between visually different options of a product, such as colors

Product ID: matches the current product seen’s ID

product.name (String)

The product’s name, which should match the name shown on the product page

Product Name: matches the current product seen’s name

product.categories (Array of Strings)

A list of one or more product categories the product belongs to. Each category is a full category path, with each level separated by >

Product category: matches one of the the current product seen’s categories

ecView

type (String)

The type of view. Can be one of basket, category, checkout, confirmation, home, product,search

Page type: matches the current page’s type

subtypes (Array of Strings)

An unordered list of subtypes to describe the view

Page subtype: matches one of the current page’s subtypes

language (String)

The language used in this view. Must be an IETF language tag (for example, en-gb, en-us)

Language: matches the current page’s language

currency (String)

The ISO 4217 currency code for the visitor (for example, GBP, USD)

Currency: matches the current page’s currency

Audiences based on visitor history

expand/collapse

Required to build an audience based on a person’s browsing or purchase history.

Event Field (JS Data Type) Description

ecProduct (PDP)

product.productId (String)

Unique identifier to distinguish between visually different options of a product, such as colors

product.categories (Array of Strings)

A list of one or more product categories the product belongs to. Each category is a full category path, with each level separated by >

product.manufacturer (String)

The product manufacturer, recommended when a product inventory includes multiple brands

eventType (String)

The type of product event (for example, detail, linked_product)

ecBasketItemTransaction (Confirmation page)

product.productId (String)

Unique identifier to distinguish between visually different options of a product, such as colors

transaction.id (String)

A unique transaction ID

Reporting

expand/collapse

Required to report metrics for campaigns and experiences.

Event Field (JS Data Type) Description

ecBasketTransactionSummary

transaction.id (String)

A unique transaction ID

basket.total.value (Number)

The basket total after the application of discounts, promotions, shipping costs, etc

basket.total.currency (String)

The ISO 4217 currency code (for example, GBP, USD)

Personalized content

expand/collapse

In the following table, you can see a breakdown of the QProtocol events and fields required for personalized content campaigns.

Event Field (JS Data Type) Description

ecView

type (String)

The type of view. Can be one of basket, category, checkout, confirmation, home, product,search

subtypes (Array of Strings)

An unordered list of subtypes to describe the view

language (String)

The language used in this view. Must be an IETF language tag (for example, en-gb, en-us)

currency (String)

The ISO 4217 currency code for the visitor (for example, GBP, USD)

ecProduct (PDP)

product.productId (String)

Unique identifier to distinguish between visually different options of a product, such as colors

product.categories (Array of Strings)

A list of one or more product categories the product belongs to. Each category is a full category path, with each level separated by >

product.name (String)

The product’s name, which should match the name shown on the product page

You can also use the following data points:

Data Point Data Source

Device

qubit.session

Browser

qubit.session

Location (city, area, region, country)

qubit.session

Current page URL

data enrichment process

Product recommendations

expand/collapse

Coveo Qubit recommendations strategies

In the following table, you can see a breakdown of the QProtocol events and fields required to deliver Product Recommendations (PR) campaigns using one of Qubit’s strategies

Event Field (JS Data Type) Description

ecView

type (String)

The type of view. Can be one of basket, category, checkout, confirmation, home, product,search

language (String)

The language used in this view. Must be an IETF language tag (for example, en-gb, en-us)

currency (String)

The ISO 4217 currency code for the visitor (for example, GBP, USD)

ecProduct

product.productId (String)

Unique identifier to distinguish between visually different options of a product, such as colors

product.price.currency (String)

The ISO 4217 currency code (for example, GBP, USD)

eventType (String)

Must be detail

ecBasketItemTransaction

transaction.id (String)

A unique transaction ID

product.productId (String)

Unique identifier to distinguish between visually different options of a product, such as colors

product.sku (String)

Unique identifier to distinguish the different variations a product can be purchased in, such as size

Google recommendations strategies

In addition to the campaign events described above, which include implementing Basic audiences, Audiences based on visitor history, and reporting, it’s necessary to emit the events shown in the following table in order to utilize the Google recommendations strategies.

Event Field (JS Data Type) Description

ecBasketItemTransaction

product.productId (String)

Unique identifier to distinguish between visually different options of a product, such as colors

product.price.value (Number)

The price of the product after any discounts, promotions, etc, rounded to 2 decimal places

product.price.currency (String)

The ISO 4217 currency code (for example, GBP, USD)

product.sku (String)

Unique identifier to distinguish the different variations a product can be purchased in, such as size

quantity (Number)

The number of products described by the line item

subtotal.value (Number)

The subtotal of the products described by the line item taking into account the current price and quantity before the application of taxes, discounts, promotions, shipping costs, etc

subtotal.currency (String)

The ISO 4217 currency code (for example, GBP, USD)

transaction.id (String)

A unique transaction ID

ecBasketItem

product.productId (String)

Unique identifier to distinguish between visually different options of a product, such as colors

product.sku (String)

Unique identifier to distinguish the different variations a product can be purchased in, such as size

ecBasketItemAction

product.productId (String)

Unique identifier to distinguish between visually different options of a product, such as colors

action (String)

add or remove

product.sku (String)

Unique identifier to distinguish the different variations a product can be purchased in, such as size

ecProduct

product.productId (String)

Unique identifier to distinguish between visually different options of a product, such as colors

product.sku (String)

Unique identifier to distinguish the different variations a product can be purchased in, such as size

product.price.value (Number)

The price of the product after discounts, promotions, etc, rounded to 2 decimal places. You do not need to include this field if it is present in the product catalog

ecView

type (String)

The type of view. Can be one of home, category, search, product, basket, checkout, confirmation, help, contact, registration, content, account, or other. Custom names can be used for unique page types that are not covered by this specification. All types must be lowercase

language (String)

The language used in this view. Must be an IETF language tag (for example, en-gb, en-us)

currency (String)

The ISO 4217 currency (for example, GBP, USD)

Product badging

expand/collapse

Campaigns

In the following table, you can see a breakdown of the QProtocol events and fields required for product badging campaigns.

Event Field (JS Data Type) Description

ecView

type (String)

The type of view. Can be one of home, category, search, product, basket, checkout, confirmation

language (String)

The language used in this view. Must be an IETF language tag (for example, en-gb, en-us)

currency (String)

The ISO 4217 currency code for the visitor (for example, GBP, USD)

ecProduct

product.productId (String)

Unique identifier to distinguish between visually different options of a product, such as colors

eventType (String)

The type of product event. Must be detail

ecBasketItemTransaction

product.productId (String)

Unique identifier to distinguish between visually different options of a product, such as colors

ecBasketItemAction

product.productId (String)

Unique identifier to distinguish between visually different options of a product, such as colors

action (String)

add or remove

product.sku (String)

Unique identifier to distinguish the different variations a product can be purchased in, such as size

Product Insights

expand/collapse

The following events are required to power Product insights.

Event Field (JS Data Type) Description

ecView

type (string)

The type of view. Must be category for PLPs, product for PDPs, and checkout for checkout pages.

language (String)

The language used in this view. Must be an IETF language tag (for example, en-gb, en-us)

currency (String)

The ISO 4217 currency code for the visitor (for example, GBP, USD)

ecProduct

product.productId (String)

Unique identifier to distinguish between visually different options of a product, such as colors

product.price.value (Number)

The price of the product after any discounts, promotions, etc, rounded to 2 decimal places. Must match price on PDP and must not be 0 or null

product.eventType (String)

The type of product event. Must be detail

ecBasketItemTransaction

product.productId (String)

Unique identifier to distinguish between visually different options of a product, such as colors

product.price.value (Number)

The price of the product after any discounts, promotions, etc, rounded to 2 decimal places. Must match price on PDP and must not be 0 or null

quantity (Number)

The number of products described by the line item

Note

Although this isn’t a requirement, we recommended that you emit the product.onSale and product.clearance fields for both ecProduct and ecBasketItemTransaction to improve product insight predictions.

Step 3: Generate product feed

To supplement the data provided by QProtocol, you’re required to provide additional product data in the form of a product feed. You can import your Google product feed into CMH to drive product recommendations.

To work with a different product feed, reach out to your CSM.

Refer to the following table for details of the required data points you need to provide.

Warning

It’s important that the product ID emitted in QProtocol events matches the ID in your product feed.

Data Point Google Feed Attribute Description Example

Product ID

id

Your product’s unique identifier

A2B4

Product name

title

Your product’s name

Mens Pique Polo Shirt

Product category

google_product_category or product_type

Product category for your product

Shirts

Product URL

link

Your product’s landing page

http://www.example.com/asp/sp.asp?cat=12&id=1030

Image URL

image_link

The URL of your product’s main image

http://www.example.com/image1.jpg

Stock or Availability

availability

Your product’s availability

In stock

Price

price

Your product’s price in the format <NUMBER> <CURRENCY>

15.00 USD

Language

Inferred from locale

en

Brand of Manufacturer

brand

Your product’s brand name

BrandA

Gender

gender

The gender for which your product is intended

female

Step 4: Tutorials

Refer to Tutorials.