Skip to content

Compass Web SDK: Integration Guide

Last updatedView as Markdown

Audience: Front-end and site developers integrating a publisher site with the Compass Web SDK.

The Compass Web SDK — published as @arcxp/compass-sdk and exposed in the browser as the global ArcCompass — is Arc XP’s first-party, client-side library for collecting reader interaction events (page views, video progress, clicks, and more) and posting them to the event collector. These events train the recommendation models.

It ships as a single bundled JavaScript file for <script> inclusion, and also as an npm ESM module for build-pipeline consumers (React / Next). The target is a roughly one-hour integration: paste a snippet, call init() once, resolve consent, and call track() for each event.

Quickstart

<script>
(function (w, d) {
w.ArcCompass = w.ArcCompass || { q: [] };
["init", "setConsent", "setUserId", "track", "trackExposure", "refreshExperiences", "flush"].forEach(function (m) {
w.ArcCompass[m] = w.ArcCompass[m] || function () { w.ArcCompass.q.push([m, [].slice.call(arguments)]); };
});
// getUserId returns a value, so it can't ride the queue: until the bundle
// attaches there is no identifier yet, so the stub returns null.
w.ArcCompass.getUserId = w.ArcCompass.getUserId || function () { return null; };
// observeRecommendations returns a handle: queue the call and return a
// deferred handle that wires to the real detector once the bundle loads.
w.ArcCompass.observeRecommendations = w.ArcCompass.observeRecommendations || function (options) {
var record = { disconnected: false, real: null };
w.ArcCompass.q.push(["observeRecommendations", [options, record]]);
return { disconnect: function () { record.disconnected = true; if (record.real) record.real.disconnect(); } };
};
var s = d.createElement("script");
s.async = true;
s.src = "https://arc-rec.app-recommend.aws.arc.pub/arc-rec/v1/compass-sdk.iife.min.js";
d.head.appendChild(s);
})(window, document);
window.ArcCompass.init({
// Events collector base path for your org. The SDK appends `/events/batch`.
domain: "https://{org}-config-prod.api.arc-cdn.net/collect-web/v1",
apiKey: "<arc-delivery-token>",
});
// Single collect-for-personalization gate: pass true once your CMP reports
// consent. Nothing is sent or persisted until then.
window.ArcCompass.setConsent(true);
window.ArcCompass.track("page_view");
</script>

The rest of this guide walks through each step.

1. Provision a delivery token

Provision a delivery token through the Arc XP Delivery API — see Provisioning tokens through the Delivery API. The SDK sends this token as the X-API-Key header on every request.

Use a key scoped to the browser events collection (the collect-web API). That key can post events but cannot reach the content catalog, so it is safe to embed in page JavaScript.

2. Add the snippet

Place this block in <head>, before any code that calls ArcCompass.*:

<script>
(function (w, d) {
w.ArcCompass = w.ArcCompass || { q: [] };
["init", "setConsent", "setUserId", "track", "trackExposure", "refreshExperiences", "flush"].forEach(function (m) {
w.ArcCompass[m] = w.ArcCompass[m] || function () { w.ArcCompass.q.push([m, [].slice.call(arguments)]); };
});
// getUserId returns a value, so it can't ride the queue: until the bundle
// attaches there is no identifier yet, so the stub returns null.
w.ArcCompass.getUserId = w.ArcCompass.getUserId || function () { return null; };
// observeRecommendations returns a handle: queue the call and return a
// deferred handle that wires to the real detector once the bundle loads.
w.ArcCompass.observeRecommendations = w.ArcCompass.observeRecommendations || function (options) {
var record = { disconnected: false, real: null };
w.ArcCompass.q.push(["observeRecommendations", [options, record]]);
return { disconnect: function () { record.disconnected = true; if (record.real) record.real.disconnect(); } };
};
var s = d.createElement("script");
s.async = true;
s.src = "https://arc-rec.app-recommend.aws.arc.pub/arc-rec/v1/compass-sdk.iife.min.js";
d.head.appendChild(s);
})(window, document);
</script>

The snippet defines a tiny stub on window.ArcCompass so calls made before the bundle loads queue up and replay in order.

The …/arc-rec/v1/… URL is major-pinned, so you receive backwards-compatible fixes automatically. For production, add Subresource Integrity (integrity + crossorigin) as shown in Distribution, which also covers exact-version pinning, the npm (ESM) install, and the self-host fallback.

3. Initialize the SDK

ArcCompass.init({
domain: "https://{org}-config-prod.api.arc-cdn.net/collect-web/v1",
apiKey: "<arc-delivery-token>",
});

domain is the browser events collector base path for your org: the shared {org}-config-prod.api.arc-cdn.net hostname followed by the collect-web/v1 prefix. The SDK appends /events/batch, so events post to https://{org}-config-prod.api.arc-cdn.net/collect-web/v1/events/batch. Arc provides the exact base URL during onboarding.

Translate whatever your CMP returns into the SDK’s single yes/no gate:

ArcCompass.setConsent(true); // collect-for-personalization granted

Events that fired before this call are flushed once consent resolves. Nothing is sent or persisted until then. See Consent & Identity for the full state machine.

5. Add the content and site meta tags

Add these tags to each page’s <head> so the SDK can attribute events to the right content and Site:

<!-- Required. The content item this page represents; it must match the ID the
content was ingested under. -->
<meta name="arc-compass:item-id" content="ARTICLE-ABC123" />
<!-- The Site this page belongs to. Optional for event attribution on a
single-site tenant; REQUIRED on every tenant if you use the SDK-rendered
recommendation or Catch Up Experiences, whose render request needs it. -->
<meta name="arc-compass:site-id" content="the-daily" />

item-id must be present for automatic page_view capture to fire. In a single-page app, set it whenever your route knows the ID — before or after navigating, or once an awaited content load resolves. The SDK fires when the tag changes, so there is no ordering to honour and no extra SDK call per route.

You can override either value for a single event with track(type, { itemId, siteId }). See Events & Reliability for the full resolution order.

6. Track events

ArcCompass.track("video_progress", { itemId: "VIDEO123", eventValue: 0.75 });

page_view is captured automatically for the item the page loads with, and again whenever the page becomes about a different item. You do not need to call track("page_view") yourself on a server-rendered page or an SPA route, unless you disable auto capture (autoPageView: false). item_id and the optional site_id come from the meta tags you set above; pass itemId or siteId to track() to override them for a single event. See Events & Reliability.

7. (Optional) Set a known user ID after login

ArcCompass.setUserId("user-abc-123");
// Pass null on logout to revert to the persistent anonymous ID.
ArcCompass.setUserId(null);

A stable, host-managed ID is what makes personalization work across devices. See Consent & Identity.

Configuration reference

ArcCompass.init(options) accepts the following options. Only domain and apiKey are required.

OptionTypeDefaultDescription
domainstring— (required)Events collector base path for your org, e.g. https://{org}-config-prod.api.arc-cdn.net/collect-web/v1. No trailing slash. The SDK appends /events/batch.
apiKeystring— (required)Arc-provisioned delivery token. Sent as X-API-Key on every request — event posts and the SDK-rendered Experiences’ recommendation reads alike.
experiencesDomainstringderived from domainOrigin the SDK-rendered Experiences read from — the Arc edge root the /experiences/… recommendation and Catch Up routes hang off. Defaults to domain with a trailing /collect-web/v<n> removed, so one domain usually configures both services. Set it explicitly only when the Experiences service is on a different host from your event collector.
cookieDomainstringdetected registrable domainCookie Domain attribute for the anonymous-ID and session cookies. Defaults to the broadest scope the browser accepts for the current host, found by probing rather than by counting labels, so ccTLDs and platform hosts share identity across subdomains without configuration. Set it explicitly to override. See Cookie domain.
batch.maxEventsnumber20Flush when the buffer reaches this many events.
batch.flushIntervalMsnumber2000Flush this many milliseconds after the last flush.
queue.maxBufferednumber500Hard cap on in-memory queued events. Oldest are dropped on overflow with a console.warn.
autoPageViewbooleantrueFire page_view automatically each time the <meta name="arc-compass:item-id"> tag resolves to a new item (covering the initial load and every SPA route change), reading the optional site_id from <meta name="arc-compass:site-id"> at the same moment. Set false to call track('page_view', { itemId }) yourself.
attributionRootElement | string | falsedocument.bodyRoot under which the SDK scans for host-rendered recommendations carrying the data-arc-compass-* binding — also the click-delegation container, so tagged elements (and elements passed to trackExposure) must live inside it. Accepts an element or a CSS selector (resolved once the DOM is ready). Narrow it to the container that holds recommendations to avoid a page-wide MutationObserver; pass false to disable the scan entirely. A structurally-broken root (invalid selector, or a non-Element value) disables the binding with a console.warn; a valid selector whose container has not mounted yet is deferred and attaches on the next trackExposure. See Recommendation Attribution.
debugbooleanfalseLog lifecycle and transport diagnostics.
onError(e: ArcCompassErrorInfo) => void—Called when the SDK abandons a batch (a terminal 4xx/429, or a 5xx/network failure that exhausts retries), and when the browser accepts no cookie scope so identity drops to per-origin (reason: "degraded", no events lost). Use it to forward otherwise-silent loss to your observability. Never runs inside a track() call, so it cannot interrupt the page; anything it throws is swallowed. See Error visibility.

Error visibility

The SDK is fire-and-forget: track() never throws, and a batch that the collector rejects (or that never gets through) is dropped rather than surfaced to the page. To make those drops observable, wire onError to your tooling — it fires for terminal transport failures and for one client-side degradation, both off the user’s interaction path:

ArcCompass.init({
domain: "https://{org}-config-prod.api.arc-cdn.net/collect-web/v1",
apiKey: "<collect-web token>",
onError: (e) => {
// e: { message, reason: "rejected" | "exhausted" | "degraded", status?, droppedEvents, eventTypes, cause? }
Sentry.captureException(e.cause ?? new Error(e.message), { extra: e });
// or: datadogRum.addError(e.cause ?? new Error(e.message), e);
},
});

reason: "rejected" is a 4xx/429 the collector refused (fix the payload or key — retrying won’t help); reason: "exhausted" means repeated 5xx/network failures used up the retry budget. status is absent for network/CORS errors (no response). reason: "degraded" loses no events at all (droppedEvents is 0): it fires at most once per page when the browser accepted no cookie scope, which costs you cross-subdomain identity — see Cookie domain. Per-event issues (an unresolved item_id, a coerced event_value) are logged only under debug, not through onError.

When cookieDomain is omitted, the SDK writes its identity cookies at the broadest scope the browser accepts for the current host, which is that host’s registrable domain. It finds the scope by offering each candidate to the browser and keeping the first one that sticks, rather than by counting labels, so multi-segment public suffixes (.co.uk, .com.au) and platform hosts (CloudFront, Vercel, Netlify, GitHub Pages) resolve correctly without configuration. Probe cookies are deleted as soon as they are read back, and none is written before consent.

Set cookieDomain explicitly to override the detected scope. If the browser accepts no scope at all, identity still works but is per-origin rather than shared across subdomains, and onError fires once with reason: "degraded" (see Error visibility).

Browser support

The SDK assumes baseline support for fetch with keepalive: true, crypto.randomUUID (with a fallback when absent), visibilitychange, and localStorage / sessionStorage / cookies.

Tested matrix:

  • Chrome / Edge / Chromium ≥ 90
  • Safari / WebKit ≥ 14
  • Firefox ≥ 88

The SDK is browser-only. The IIFE bundle is safe to include in a server-rendered page — it no-ops on import when window is undefined.

See also