--- title: 'Custom implementations: Track events without the Coveo app for Shopify' slug: p2la0423 canonical_url: https://docs.coveo.com/en/p2la0423/ collection: coveo-for-commerce source_format: adoc --- # Custom implementations: Track events without the Coveo app for Shopify [Coveo Analytics events](https://docs.coveo.com/en/260/) are crucial for analyzing your [storefront](https://docs.coveo.com/en/p33g0410/) performance and powering [Coveo Machine Learning (Coveo ML)](https://docs.coveo.com/en/188/) [models](https://docs.coveo.com/en/1012/). This article explains how to track commerce events if you're not using the **Coveo AI Search and Discovery** app. If you're using the app, see [Track Shopify events using the Coveo AI Search & Discovery app](https://docs.coveo.com/en/oc2d1152/). ## Types of events There are four types of commerce events: * [Click events](https://docs.coveo.com/en/o1n92447/) * [Cart events](https://docs.coveo.com/en/o1n93466/) * [Product view events](https://docs.coveo.com/en/o1n93101/) * [Purchase events](https://docs.coveo.com/en/o1n93059/) > **Note** > > Under the [Coveo Event Protocol (EP)](https://docs.coveo.com/en/o3r90189/), the Commerce API logs search events server-side automatically for you. ## Headless The appropriate Headless controllers log click, cart, and product view events. * To log click and product view events, see [Displaying products](https://docs.coveo.com/en/p24a0093/). * To log cart events, see [Managing the cart](https://docs.coveo.com/en/p1oe0470/). Headless can't log purchase events in your store. To log purchase events: . Implement an [app web pixel](https://shopify.dev/docs/apps/build/marketing-analytics/build-web-pixels) that uses [Coveo Relay](https://docs.coveo.com/en/relay/latest/). . Add the required attributes to your Shopify cart so the web pixel can track these events correctly. For step-by-step instructions and code samples on how to set these attributes in your storefront, see [Event tracking with Headless (Shopify Hydrogen)](https://docs.coveo.com/en/p3qc0468#purchase-events). This section explains how to implement the app web pixel. . Create a [web pixel extension](https://shopify.dev/docs/apps/build/marketing-analytics/build-web-pixels) and configure it as follows: ``` # shopify.extension.toml name = "coveo-analytics-web-pixel" type = "web_pixel_extension" runtime_context = "strict" [customer_privacy] analytics = true <1> marketing = false preferences = false sale_of_data = "disabled" [settings] type = "object" [settings.fields.organizationId] <2> name = "Organization ID" description = "Your Coveo organization ID." type = "single_line_text_field" validations = [{ name = "min", value = "1" }] ``` <1> Set the [`analytics`](https://shopify.dev/docs/apps/build/marketing-analytics/build-web-pixels#step-2-define-your-web-pixel-settings) setting to `true` because the app web pixel will log analytics events. <2> When [activating your web pixel](https://shopify.dev/docs/apps/build/marketing-analytics/build-web-pixels#step-5-activate-a-web-pixel-extension), you'll pass your Coveo organization ID in the `organizationId` setting. . Install the required dependencies, namely [@coveo/relay](https://www.npmjs.com/package/@coveo/relay?activeTab=readme), [@coveo/relay-event-types](https://www.npmjs.com/package/@coveo/relay-event-types?activeTab=readme), and [`@coveo/shopify`](https://www.npmjs.com/package/@coveo/shopify). ```json // package.json { // ... "dependencies": { // ... "@coveo/relay": "^1.2.0", "@coveo/relay-event-types": "^13.1.3", "@coveo/shopify": "1.3.0", } } ``` . Create a helper file to build the target Coveo Relay events. ```ts // src/ecEventsBuilders.ts import { CurrencyCodeISO4217, ProductQuantity } from "@coveo/relay-event-types"; import { Checkout, CheckoutLineItem } from "@shopify/web-pixels-extension"; import { Ec } from "@coveo/relay-event-types"; export const ecPurchaseEvent = (checkout: Checkout): Ec.Purchase => ({ <1> currency: checkout.currencyCode?.toUpperCase() as CurrencyCodeISO4217, products: checkout.lineItems.map(lineItemObject), transaction: { id: checkout.token ?? "", revenue: checkout.totalPrice?.amount ?? 0, }, }); const lineItemObject = (lineItem: CheckoutLineItem): ProductQuantity => ({ quantity: lineItem.quantity, product: { productId: lineItem.id ?? "", name: lineItem.title ?? "", price: lineItemPrice(lineItem), }, }); const lineItemPrice = (lineItem: CheckoutLineItem): number => (lineItem.finalLinePrice?.amount ?? 0) / lineItem.quantity; ``` <1> Build the [purchase event](https://docs.coveo.com/en/o1n93059/). . Create a Relay environment helper file for [client ID](https://docs.coveo.com/en/lbjf0131/) and context management. Outside of the Shopify web pixel, Relay can manage the client ID and context automatically, but the restrictive nature of the web pixel requires a custom implementation. ```ts // relayEnvironment.ts import type { CustomEnvironment } from "@coveo/relay"; import { ExtensionApi } from "@shopify/web-pixels-extension"; export const webPixelRelayEnvironment = ({ init }: ExtensionApi, clientId: string): CustomEnvironment => { return { generateUUID: () => clientId, <1> getLocation: () => init.context.document.location.href, getReferrer: () => init.context.document.referrer, getUserAgent: () => init.context.navigator.userAgent, send: (url, token, event) => fetch(`${url}?access_token=${token}`, { method: "POST", headers: { "Content-Type": "application/json", }, keepalive: true, body: JSON.stringify([event]), }), }; }; ``` <1> This [client ID](https://docs.coveo.com/en/lbjf0131/) will be retrieved from the standard Shopify [`checkout_completed`](https://shopify.dev/docs/api/web-pixels-api/standard-events/checkout_completed) event emitted by the storefront, which will be handled in the next step. . Create a `getCoveoDataFromShopifyEvent.ts` file that listens for the standard Shopify [`checkout_completed`](https://shopify.dev/docs/api/web-pixels-api/standard-events/checkout_completed) event and extracts the relevant data to build the Relay instance. ```ts // getCoveoDataFromShopifyEvent.ts import { ExtensionApi } from "@shopify/web-pixels-extension"; import { COVEO_SHOPIFY_CHECKOUT_KEY } from "@coveo/shopify/utilities"; <1> import { createStorage } from "./storage"; <2> import { extractCoveoSettingsFromCartAttributes } from "./extractCoveoSettingsFromCartAttributes"; export interface CoveoShopifyEvent { <3> accessToken: string; organizationId: string; trackingId: string; clientId: string; } export const registerCoveoShopifyEventListener = ({ analytics, browser }: ExtensionApi) => { const storage = createStorage(browser); let resolveCoveoShopifyEvent: (value: CoveoShopifyEvent) => void; <4> const coveoShopifyEventPromise = new Promise((resolve) => { resolveCoveoShopifyEvent = resolve; }); analytics.subscribe(COVEO_SHOPIFY_CHECKOUT_KEY, async (event) => { <5> const checkout = event.data?.checkout; const settings = extractCoveoSettingsFromCartAttributes(checkout); <6> if (validateCoveoShopifyEvent(settings)) { <7> resolveCoveoShopifyEvent(settings); await storage.set(COVEO_SHOPIFY_CHECKOUT_KEY, settings); } }); const getCoveoShopifyEvent = async () => { const settings = await storage.get(COVEO_SHOPIFY_CHECKOUT_KEY); if (validateCoveoShopifyEvent(settings)) { <8> return settings; } return coveoShopifyEventPromise; <9> }; return { getCoveoShopifyEvent }; }; const validateCoveoShopifyEvent = (settings: unknown): settings is CoveoShopifyEvent => { const coveoShopifyEvent = settings as CoveoShopifyEvent; if ( coveoShopifyEvent && typeof coveoShopifyEvent === "object" && typeof coveoShopifyEvent.organizationId === "string" && coveoShopifyEvent?.organizationId?.length && typeof coveoShopifyEvent.trackingId === "string" && coveoShopifyEvent?.trackingId?.length && typeof coveoShopifyEvent.accessToken === "string" && coveoShopifyEvent?.accessToken?.length && typeof coveoShopifyEvent.clientId === "string" && coveoShopifyEvent?.clientId?.length ) { return true; } else { console.warn(`Received Coveo Shopify event with invalid settings: ${JSON.stringify(settings)}`); return false; } }; ``` <1> The [@coveo/shopify](https://www.npmjs.com/package/@coveo/shopify) package exports the `COVEO_SHOPIFY_CONFIG_KEY` constant, which is used to identify the custom event name emitted by the storefront. <2> The `createStorage` helper function will be defined next. <3> The interface for the custom event data that will be emitted by the storefront and used to create the Relay instance. <4> A promise that will be resolved with the Coveo Shopify event data when the custom event is received. <5> [Subscribe](https://shopify.dev/docs/api/web-pixels-api/standard-api/analytics) to the custom event emitted by the storefront. <6> In the next step, you'll create the `extractCoveoSettingsFromCartAttributes` function that extracts the Coveo configuration from the cart attributes, returning it as a structured object. <7> Validate the custom data to ensure it contains the required properties. Then, resolve the promise with the settings and store them in the browser storage. <8> If the settings are valid, return them. <9> If the settings aren't valid or not available, return the promise that will be resolved when the custom event is received. This allows the web pixel to wait for the custom event to be emitted by the storefront before proceeding with the Relay instance creation and event logging. . Create a `cartAttributeExtractor.ts` file that extracts the Coveo configuration from the cart attributes and returns it in a structured way. ```ts // cartAttributeExtractor.ts import type { CoveoShopifyEvent } from "./getCoveoDataFromShopifyEvent"; <1> export interface CartAttribute { key: string; value: string; } export interface CheckoutWithAttributes { attributes?: CartAttribute[]; } export const CART_ATTRIBUTE_KEYS = { <2> CLIENT_ID: "coveoClientId", TRACKING_ID: "coveoTrackingId", ORGANIZATION_ID: "coveoOrganizationId", ACCESS_TOKEN: "coveoAccessToken", } as const; export const extractCoveoSettingsFromCartAttributes = ( checkout: CheckoutWithAttributes, ): Partial | undefined => { if (!checkout?.attributes || !Array.isArray(checkout.attributes)) { return undefined; } const attrMap = checkout.attributes.reduce>((acc, { key, value }) => { acc[key] = value; return acc; }, {}); <3> if ( <4> !attrMap[CART_ATTRIBUTE_KEYS.CLIENT_ID] && !attrMap[CART_ATTRIBUTE_KEYS.TRACKING_ID] && !attrMap[CART_ATTRIBUTE_KEYS.ORGANIZATION_ID] && !attrMap[CART_ATTRIBUTE_KEYS.ACCESS_TOKEN] ) { return undefined; } return { <5> clientId: attrMap[CART_ATTRIBUTE_KEYS.CLIENT_ID], trackingId: attrMap[CART_ATTRIBUTE_KEYS.TRACKING_ID], organizationId: attrMap[CART_ATTRIBUTE_KEYS.ORGANIZATION_ID], accessToken: attrMap[CART_ATTRIBUTE_KEYS.ACCESS_TOKEN], }; }; ``` <1> Import the `CoveoShopifyEvent` interface from the previous step to ensure the returned object matches the expected structure. <2> Define constants for the cart attribute keys that will hold the Coveo configuration values. <3> Convert the array of cart attributes into a key-value map for easier access. <4> Check if any of the required Coveo configuration attributes are present. <5> Returns the extracted settings in a structured format, specifically the `CoveoShopifyEvent` interface. . Create the helper storage file. ```ts // src/storage.ts import { ExtensionApi } from "@shopify/web-pixels-extension"; export const createStorage = (browser: ExtensionApi["browser"]) => ({ get: async (key: string) => { const value = (await browser.cookie.get(key)) || (await browser.localStorage.getItem(key)); if (value) { try { return JSON.parse(value) as T; } catch { return; } } }, set: async (key: string, value: T) => { <1> const encodedValue = JSON.stringify(value); await browser.cookie.set(key, encodedValue); await browser.localStorage.setItem(key, encodedValue); }, }); ``` <1> Store the value in both the cookie and local storage to improve reliability. . Populate the `index.ts` file to register the web pixel extension. ```ts // src/index.ts import { createRelay } from "@coveo/relay"; import { ExtensionApi, register } from "@shopify/web-pixels-extension"; import { ecAddToCartEvent, ecProductViewEvent, ecPurchaseEvent, ecRemoveFromCartEvent } from "./ecEventBuilders.ts"; import { webPixelRelayEnvironment } from "./relayEnvironment"; import { name as webPixelPackageName, version as webPixelPackageVersion } from "../package.json"; import { registerCoveoShopifyEventListener, CoveoShopifyEvent } from "./getCoveoShopifyEvent.ts"; register((extensionAPI: ExtensionApi) => { const { analytics } = extensionAPI; const { getCoveoShopifyEvent } = registerCoveoShopifyEventListener(extensionAPI); const relayPromise = createRelayPromise(extensionAPI, getCoveoShopifyEvent); analytics.subscribe("checkout_completed", async (event) => (await relayPromise).emit("ec.purchase", ecPurchaseEvent(event.data.checkout)), ); }); const createRelayPromise = async ( extensionAPI: ExtensionApi, getCoveoShopifyEvent: () => Promise, ) => { const { clientId, trackingId, accessToken } = await getCoveoShopifyEvent(); const relay = createRelay({ trackingId, token: accessToken, url: `https://${settings.organizationId}.analytics.org.coveo.com/rest/organizations/${settings.organizationId}/events/v1`, <1> environment: webPixelRelayEnvironment(extensionAPI, clientId), source: [`${webPixelPackageName}@${webPixelPackageVersion}`], }); return relay; }; ``` <1> Use the organization ID in the web pixel settings to configure the endpoint. It would also be possible to retrieve this ID from the custom event data. . When [activating your web pixel](https://shopify.dev/docs/apps/build/marketing-analytics/build-web-pixels#step-5-activate-a-web-pixel-extension), pass your [Coveo organization ID](https://docs.coveo.com/en/n1ce5273/) in the `organizationId` setting. ```graphql mutation { webPixelCreate(webPixel: { settings: "{\"organizationId\":\"myshoprzh5nbge\"}" }) { userErrors { code field message } webPixel { settings id } } } ``` ## Atomic Atomic logs click events automatically. We recommend creating an [app web pixel](https://shopify.dev/docs/apps/build/marketing-analytics/build-web-pixels) with [Coveo Relay](https://docs.coveo.com/en/relay/latest/) to track cart, product, and purchase events, when the following events are logged from your store, respectively: * [`product_added_to_cart`](https://shopify.dev/docs/api/web-pixels-api/standard-events/product_added_to_cart) or [`product_removed_from_cart`](https://shopify.dev/docs/api/web-pixels-api/standard-events/product_removed_from_cart) * [`product_viewed`](https://shopify.dev/docs/api/web-pixels-api/standard-events/product_viewed) * [`checkout_completed`](https://shopify.dev/docs/api/web-pixels-api/standard-events/checkout_completed) The following implementation example explains how to do so. . Create a [web pixel extension](https://shopify.dev/docs/apps/build/marketing-analytics/build-web-pixels) and configure it as follows: ``` # shopify.extension.toml name = "coveo-analytics-web-pixel" type = "web_pixel_extension" runtime_context = "strict" [customer_privacy] analytics = true <1> marketing = false preferences = false sale_of_data = "disabled" [settings] type = "object" [settings.fields.organizationId] <2> name = "Organization ID" description = "Your Coveo organization ID." type = "single_line_text_field" validations = [{ name = "min", value = "1" }] ``` <1> Set the [`analytics`](https://shopify.dev/docs/apps/build/marketing-analytics/build-web-pixels#step-2-define-your-web-pixel-settings) setting to `true` because the app web pixel will log analytics events. <2> When [activating your web pixel](https://shopify.dev/docs/apps/build/marketing-analytics/build-web-pixels#step-5-activate-a-web-pixel-extension), you'll pass your Coveo organization ID in the `organizationId` setting. . Install the required dependencies, namely [@coveo/relay](https://www.npmjs.com/package/@coveo/relay?activeTab=readme), [@coveo/relay-event-types](https://www.npmjs.com/package/@coveo/relay-event-types?activeTab=readme), and [`@coveo/shopify`](https://www.npmjs.com/package/@coveo/shopify). ```json // package.json { // ... "dependencies": { // ... "@coveo/relay": "^1.2.0", "@coveo/relay-event-types": "^13.1.3", "@coveo/shopify": "1.3.0", } } ``` . Create a helper file to build the target Coveo Relay events. ```ts // src/ecEventsBuilders.ts import { CurrencyCodeISO4217, Product, ProductQuantity } from "@coveo/relay-event-types"; import { CartLine, ProductVariant, Checkout, CheckoutLineItem } from "@shopify/web-pixels-extension"; import { Ec } from "@coveo/relay-event-types"; export const ecProductViewEvent = (product: ProductVariant): Ec.ProductView => ({ <1> currency: product.price.currencyCode.toUpperCase() as CurrencyCodeISO4217, product: productObject(product), }); export const ecAddToCartEvent = (cartLine: CartLine): Ec.CartAction => ({ <2> action: "add", currency: cartLine?.cost.totalAmount.currencyCode.toUpperCase() as CurrencyCodeISO4217, quantity: cartLine?.quantity, product: productObject(cartLine.merchandise), }); export const ecRemoveFromCartEvent = (cartLine: CartLine): Ec.CartAction => ({ <3> action: "remove", currency: cartLine.cost.totalAmount.currencyCode.toUpperCase() as CurrencyCodeISO4217, quantity: cartLine.quantity, product: productObject(cartLine.merchandise), }); const productObject = (product: ProductVariant): Product => ({ productId: product.id || "", name: product.title || "", price: product.price.amount || 0, }); export const ecPurchaseEvent = (checkout: Checkout): Ec.Purchase => ({ <4> currency: checkout.currencyCode?.toUpperCase() as CurrencyCodeISO4217, products: checkout.lineItems.map(lineItemObject), transaction: { id: checkout.token ?? "", revenue: checkout.totalPrice?.amount ?? 0, }, }); const lineItemObject = (lineItem: CheckoutLineItem): ProductQuantity => ({ quantity: lineItem.quantity, product: { productId: lineItem.id ?? "", name: lineItem.title ?? "", price: lineItemPrice(lineItem), }, }); const lineItemPrice = (lineItem: CheckoutLineItem): number => (lineItem.finalLinePrice?.amount ?? 0) / lineItem.quantity; ``` <1> Build the [product view event](https://docs.coveo.com/en/o1n93101/). <2> Build the [add to cart event](https://docs.coveo.com/en/o1n93466/). <3> Build the [remove from cart event](https://docs.coveo.com/en/o1n93466/). <4> Build the [purchase event](https://docs.coveo.com/en/o1n93059/). . Create a Relay environment helper file for [client ID](https://docs.coveo.com/en/lbjf0131/) and context management. Outside of the Shopify web pixel, Relay can manage the client ID and context automatically, but the restrictive nature of the web pixel requires a custom implementation. ```ts // relayEnvironment.ts import type { CustomEnvironment } from "@coveo/relay"; import { ExtensionApi } from "@shopify/web-pixels-extension"; export const webPixelRelayEnvironment = ({ init }: ExtensionApi, clientId: string): CustomEnvironment => { return { generateUUID: () => clientId, <1> getLocation: () => init.context.document.location.href, getReferrer: () => init.context.document.referrer, getUserAgent: () => init.context.navigator.userAgent, send: (url, token, event) => fetch(`${url}?access_token=${token}`, { method: "POST", headers: { "Content-Type": "application/json", }, keepalive: true, body: JSON.stringify([event]), }), }; }; ``` <1> This [client ID](https://docs.coveo.com/en/lbjf0131/) will be retrieved from a `coveo_shopify_config` [custom event](https://shopify.dev/docs/api/web-pixels-api/emitting-data) emitted by the storefront, which will be handled in the next step. . Create a `getCoveoShopifyEvent.ts` file that listens to the custom event emitted by your storefront and extracts the relevant data to build the Relay instance. Use the `init` function from the [@coveo/shopify](https://www.npmjs.com/package/@coveo/shopify) library to [initialize your web pixel](https://docs.coveo.com/en/oc2d1157#initialize-the-web-pixel). The `init` function emits a custom event named `coveo_shopify_config` with the relevant data. ```ts // getCoveoShopifyEvent.ts import { ExtensionApi } from "@shopify/web-pixels-extension"; import { COVEO_SHOPIFY_CONFIG_KEY } from "@coveo/shopify/utilities"; <1> import { createStorage } from "./storage"; <2> export interface CoveoShopifyEvent { <3> accessToken: string; organizationId: string; trackingId: string; clientId: string; } export const registerCoveoShopifyEventListener = ({ analytics, browser }: ExtensionApi) => { const storage = createStorage(browser); let resolveCoveoShopifyEvent: (value: CoveoShopifyEvent) => void; <4> const coveoShopifyEventPromise = new Promise((resolve) => { resolveCoveoShopifyEvent = resolve; }); analytics.subscribe(COVEO_SHOPIFY_CONFIG_KEY, async (event) => { <5> const settings = event.customData as unknown as CoveoShopifyEvent; <6> if (validateCoveoShopifyEvent(settings)) { <7> resolveCoveoShopifyEvent(settings); await storage.set(COVEO_SHOPIFY_CONFIG_KEY, settings); } }); const getCoveoShopifyEvent = async () => { const settings = await storage.get(COVEO_SHOPIFY_CONFIG_KEY); if (validateCoveoShopifyEvent(settings)) { <8> return settings; } return coveoShopifyEventPromise; <9> }; return { getCoveoShopifyEvent }; }; const validateCoveoShopifyEvent = (settings: unknown): settings is CoveoShopifyEvent => { const coveoShopifyEvent = settings as CoveoShopifyEvent; if ( coveoShopifyEvent && typeof coveoShopifyEvent === "object" && typeof coveoShopifyEvent.organizationId === "string" && coveoShopifyEvent?.organizationId?.length && typeof coveoShopifyEvent.trackingId === "string" && coveoShopifyEvent?.trackingId?.length && typeof coveoShopifyEvent.accessToken === "string" && coveoShopifyEvent?.accessToken?.length && typeof coveoShopifyEvent.clientId === "string" && coveoShopifyEvent?.clientId?.length ) { return true; } else { console.warn(`Received Coveo Shopify event with invalid settings: ${JSON.stringify(settings)}`); return false; } }; ``` <1> The [@coveo/shopify](https://www.npmjs.com/package/@coveo/shopify) package exports the `COVEO_SHOPIFY_CONFIG_KEY` constant, which is used to identify the custom event name emitted by the storefront. <2> The `createStorage` helper function will be defined next. <3> The interface for the custom event data that will be emitted by the storefront and used to create the Relay instance. <4> A promise that will be resolved with the Coveo Shopify event data when the custom event is received. <5> [Subscribe](https://shopify.dev/docs/api/web-pixels-api/standard-api/analytics) to the custom event emitted by the storefront. <6> The custom data of the event is expected to be of type `CoveoShopifyEvent`. <7> Validate the custom data to ensure it contains the required properties. Then, resolve the promise with the settings and store them in the browser storage. <8> If the settings are valid, return them. <9> If the settings aren't valid or not available, return the promise that will be resolved when the custom event is received. This allows the web pixel to wait for the custom event to be emitted by the storefront before proceeding with the Relay instance creation and event logging. . Create the helper storage file. ```ts // src/storage.ts import { ExtensionApi } from "@shopify/web-pixels-extension"; export const createStorage = (browser: ExtensionApi["browser"]) => ({ get: async (key: string) => { const value = (await browser.cookie.get(key)) || (await browser.localStorage.getItem(key)); if (value) { try { return JSON.parse(value) as T; } catch { return; } } }, set: async (key: string, value: T) => { <1> const encodedValue = JSON.stringify(value); await browser.cookie.set(key, encodedValue); await browser.localStorage.setItem(key, encodedValue); }, }); ``` <1> Store the value in both the cookie and local storage to improve reliability. . Populate the `index.ts` file to register the web pixel extension. ```ts // src/index.ts import { createRelay } from "@coveo/relay"; import { ExtensionApi, register } from "@shopify/web-pixels-extension"; import { ecAddToCartEvent, ecProductViewEvent, ecPurchaseEvent, ecRemoveFromCartEvent } from "./ecEventBuilders.ts"; import { webPixelRelayEnvironment } from "./relayEnvironment"; import { name as webPixelPackageName, version as webPixelPackageVersion } from "../package.json"; import { registerCoveoShopifyEventListener, CoveoShopifyEvent } from "./getCoveoShopifyEvent.ts"; register((extensionAPI: ExtensionApi) => { const { analytics } = extensionAPI; const { getCoveoShopifyEvent } = registerCoveoShopifyEventListener(extensionAPI); const relayPromise = createRelayPromise(extensionAPI, getCoveoShopifyEvent); analytics.subscribe("checkout_completed", async (event) => (await relayPromise).emit("ec.purchase", ecPurchaseEvent(event.data.checkout)), ); analytics.subscribe("product_viewed", async (event) => (await relayPromise).emit("ec.productView", ecProductViewEvent(event.data.productVariant)), ); analytics.subscribe("product_added_to_cart", async (event) => { if (!event.data.cartLine) { return; } (await relayPromise).emit("ec.cartAction", ecAddToCartEvent(event.data.cartLine)); }); analytics.subscribe("product_removed_from_cart", async (event) => { if (!event.data.cartLine) { return; } (await relayPromise).emit("ec.cartAction", ecRemoveFromCartEvent(event.data.cartLine)); }); }); const createRelayPromise = async ( extensionAPI: ExtensionApi, getCoveoShopifyEvent: () => Promise, ) => { const { clientId, trackingId, accessToken } = await getCoveoShopifyEvent(); const relay = createRelay({ trackingId, token: accessToken, url: `https://${settings.organizationId}.analytics.org.coveo.com/rest/organizations/${settings.organizationId}/events/v1`, <1> environment: webPixelRelayEnvironment(extensionAPI, clientId), source: [`${webPixelPackageName}@${webPixelPackageVersion}`], }); return relay; }; ``` <1> Use the organization ID in the web pixel settings to configure the endpoint. It would also be possible to retrieve this ID from the custom event data. . When [activating your web pixel](https://shopify.dev/docs/apps/build/marketing-analytics/build-web-pixels#step-5-activate-a-web-pixel-extension), pass your [Coveo organization ID](https://docs.coveo.com/en/n1ce5273/) in the `organizationId` setting. ```graphql mutation { webPixelCreate(webPixel: { settings: "{\"organizationId\":\"myshoprzh5nbge\"}" }) { userErrors { code field message } webPixel { settings id } } } ```