# Compass Web SDK: Integration Guide

Source: https://dev.arcxp.com/content-recommendations/sdk/integration-guide/

Add the Compass Web SDK (ArcCompass) to your site: paste the loader snippet, initialize the client, resolve consent, and start collecting browser events that power personalized recommendations.

**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

```html
<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](https://dev.arcxp.com/platform-tools/developer-docs/provisioning-tokens-through-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.

> **Caution:**
> 
> Do not use a `collector` (content-ingestion) key here. Only a browser-events key belongs in client-side code.

> **Note:**
> 
> **Rendering context.** The same `init()` that sets up event collection also sets up the SDK-rendered recommendation Experiences (see [Recommendations Components](https://dev.arcxp.com/content-recommendations/sdk/recommendations-components/)). Those Experiences **read** recommendations from the browser — a different Arc service from the event collector, but one reached through the **same delivery token and the same `init()` call**. There is no second key to provision: the SDK sends your `apiKey` as `X-API-Key` on an Experience read just as it does on an event post.
> 
> The reads go to a different origin, too — the Arc edge root, where the Experience routes live (`/experiences/…`), rather than the `collect-web` ingest path your events post to. The SDK derives that origin from your `domain` automatically (it strips the trailing `/collect-web/v1`), so a single `domain` usually configures both services. Set the [experiencesDomain](#configuration-reference) option only when the Experiences service and your event collector are on different hosts for your tenant.
> 
> Rendering also needs a resolved `site_id` (from the site meta tag below — required for rendering **even on a single-site tenant**) and a reader identity, both of which this guide already establishes.

## 2\. Add the snippet

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

```html
<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](https://dev.arcxp.com/content-recommendations/sdk/distribution/), which also covers exact-version pinning, the npm (ESM) install, and the self-host fallback.

## 3\. Initialize the SDK

```js
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:

```js
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](https://dev.arcxp.com/content-recommendations/sdk/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:

```html
<!-- 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](https://dev.arcxp.com/content-recommendations/sdk/events-reliability/) for the full resolution order.

## 6\. Track events

```js
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](https://dev.arcxp.com/content-recommendations/sdk/events-reliability/).

> **Note:**
> 
> The `item_id` you send **must** match the ID the content was ingested under. A mismatch orphans interaction history and breaks recommendation quality. See [Item ID Guidance](https://dev.arcxp.com/content-recommendations/content-recommendations-item-id-guidance/).

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

```js
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](https://dev.arcxp.com/content-recommendations/sdk/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](#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](https://dev.arcxp.com/content-recommendations/sdk/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). |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |

### 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:

```ts
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](#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](#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](https://dev.arcxp.com/content-recommendations/content-recommendations-api-developer-guide/#choosing-how-to-collect-behavioral-events) — how this path fits the whole integration, and when to use a forwarder instead.
- [Compass Web SDK: Consent & Identity](https://dev.arcxp.com/content-recommendations/sdk/consent-identity/)
- [Compass Web SDK: Events & Reliability](https://dev.arcxp.com/content-recommendations/sdk/events-reliability/)
- [Compass Web SDK: Distribution & FAQ](https://dev.arcxp.com/content-recommendations/sdk/distribution/)
- [Compass Onboarding Checklist](https://dev.arcxp.com/content-recommendations/content-recommendations-onboarding-checklist/)
