Compass Web SDK: Integration Guide
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.
4. Resolve consent
Translate whatever your CMP returns into the SDK’s single yes/no gate:
ArcCompass.setConsent(true); // collect-for-personalization grantedEvents 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.
| Option | Type | Default | Description |
|---|---|---|---|
domain | string | — (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. |
apiKey | string | — (required) | Arc-provisioned delivery token. Sent as X-API-Key on every request — event posts and the SDK-rendered Experiences’ recommendation reads alike. |
experiencesDomain | string | derived from domain | Origin 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. |
cookieDomain | string | detected registrable domain | Cookie 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.maxEvents | number | 20 | Flush when the buffer reaches this many events. |
batch.flushIntervalMs | number | 2000 | Flush this many milliseconds after the last flush. |
queue.maxBuffered | number | 500 | Hard cap on in-memory queued events. Oldest are dropped on overflow with a console.warn. |
autoPageView | boolean | true | Fire 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. |
attributionRoot | Element | string | false | document.body | Root 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. |
debug | boolean | false | Log 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.
Cookie domain
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
- Compass API Developer Guide — how this path fits the whole integration, and when to use a forwarder instead.
- Compass Web SDK: Consent & Identity
- Compass Web SDK: Events & Reliability
- Compass Web SDK: Distribution & FAQ
- Compass Onboarding Checklist