# Compass Web SDK: Consent & Identity

Source: https://dev.arcxp.com/content-recommendations/sdk/consent-identity/

How the Compass Web SDK gates collection on a single consent decision, how it resolves reader identity (host-set IDs and the anonymous fallback), and how to keep the recommend read path coherent with getUserId().

Consent and identity are tightly coupled in the Web SDK: no identifier is created, read, or persisted until consent is granted, and the identity the SDK resolves is what personalization is keyed on. This page covers both.

## Consent

The SDK is CMP-agnostic. It does not parse IAB TCF / GPP / vendor strings itself; the host translates whatever its CMP returns into a single yes/no decision and passes it to `setConsent`.

### Single gate

The Web SDK has one consent purpose — **collect-for-personalization** — expressed as a plain boolean. In v1 there is no separate analytics-only collection mode and no ephemeral-ID “soft-gate”: consent granted means collect with a stable identifier; not granted means collect nothing.

```ts
ArcCompass.setConsent(granted: boolean);
```

### State machine

| State   | Trigger                    | Behavior                                                                                      |
| ------- | -------------------------- | --------------------------------------------------------------------------------------------- |
| unknown | Initial; before setConsent | Events queue in memory (subject to the 500-event cap) and wait. Nothing is sent or persisted. |
| granted | setConsent(true)           | Queue drains; events fire normally; future events flow live under a stable ID.                |
| denied  | setConsent(false)          | Queue is dropped; any persistent ID is wiped; future events are dropped at track() time.      |

Consent can be revised at any time — calling `setConsent` again applies the new decision to subsequent events. Events already in flight when consent changes are not recalled.

### No pre-consent leak

Before `setConsent` is called, the SDK makes **no network request and no persistent-storage write**. Events captured pre-consent buffer in memory and flush on grant; on denial the buffer is dropped and any persistent ID is wiped.

### Mapping a CMP to the gate

The host maps its CMP’s signal to the single boolean, e.g.:

```js
// OneTrust
window.OptanonWrapper = function () {
  const groups = OptanonActiveGroups.split(",");
  ArcCompass.setConsent(groups.includes("C0004")); // personalization group
};
```

## Identity & sessions

Identity is **host-primary**. The SDK uses the ID you set via `setUserId()`as-is, and generates its own anonymous ID only when you do not set one.

### `setUserId(userId)` — the preferred path

Call this with the ID you already have for the visitor. It may be either:

- a **logged-in user ID**, or
- your own **anonymous / visitor ID** (many sites already assign one to signed-out visitors; if you have it, pass it).

A stable, host-managed ID is what makes personalization work **across devices**: the same ID on a phone and a laptop shares one history. Prefer it over the SDK’s per-device fallback whenever you can.

```js
ArcCompass.setUserId("user-abc-123"); // logged-in or host visitor ID
ArcCompass.setUserId(null); // revert to the SDK anonymous fallback
```

The value is held **in memory only** — the SDK never writes it to a cookie or storage. You own its persistence. A blank or whitespace-only string is treated the same as `null`.

### Anonymous fallback — only when you set no ID

When no host ID is set (and consent is granted), the SDK generates a UUID and writes it to:

- a first-party cookie `arccompass_aid` with `Domain=<cookieDomain>; Max-Age=63072000; SameSite=Lax` (`Secure` is added on `https` origins), and
- `localStorage` under the same key, as a mirror.

Read priority: cookie → localStorage → generate. If a value is found in one store but not the other, the SDK writes it back to repair the mirror. This fallback is per-device and less durable than a host-managed ID, but it makes a low-effort, out-of-the-box integration work with no identity wiring. A host-set ID always takes precedence over `arccompass_aid`.

`cookieDomain` defaults to the broadest scope the browser accepts for the current host, which is its registrable domain: `.example.com` for `www.example.com`, `.example.co.uk` for `www.example.co.uk`, and `.d111111abcdef8.cloudfront.net` for a CloudFront distribution. The SDK finds it by offering each candidate scope to the browser and keeping the first one that sticks, so identity is shared across your subdomains with no configuration, and no Public Suffix List has to ship in the bundle. Pass `cookieDomain` in `init()` to override that.

If the browser accepts no scope at all — cookies switched off, or a page served from a public suffix itself — the SDK keeps working per-origin out of `localStorage` and reports it once through [onError](https://dev.arcxp.com/content-recommendations/sdk/integration-guide/#error-visibility)with `reason: "degraded"`. Identity is not shared across subdomains in that state: the same reader on `www.` and on `sport.` resolves to two different `arccompass_aid` values.

### `getUserId()` — keep the recommend read path coherent

The `user_id` the SDK puts on events must be the **same raw value** your recommend read-path query uses. The event collector and the recommendation read API pseudonymize `user_id` identically, so personalization silently breaks if the two paths send different raw values.

`getUserId()` returns the SDK’s resolved identifier so you can forward the identical value:

```js
// When calling the recommend API from the browser:
const userId = ArcCompass.getUserId();
if (userId) {
  const res = await fetch(`${RECOMMEND_URL}?user_id=${encodeURIComponent(userId)}`, {
    headers: { "X-API-Key": deliveryToken },
  });
  // ...render recommendations
}
```

It returns:

- the **host-set ID** when `setUserId()` has been called (regardless of consent — it is your value, never persisted by the SDK);
- otherwise the **persistent anonymous ID** once consent is granted;
- otherwise `null` — before consent (nothing may be read or written yet, and there is nothing to personalize) or before `init()`.

> **Note:**
> 
> `getUserId()` is a synchronous getter that returns a value, so it cannot ride the pre-load snippet queue (a queued call returns nothing). The loader snippet stubs it to return `null` until the bundle attaches — the same value the live SDK returns before `init()` — so calling it early is **safe**; it just reports “no identifier yet”.

> **Note:**
> 
> **No identity stitching** in this iteration. Events emitted under the anonymous ID are not retroactively linked to a later known user. This is a potential future extension.

### Session ID

The SDK manages a session ID transparently:

- generated on the first event and stored in `sessionStorage` (`arccompass_sid`) for the tab;
- mirrored in a short-TTL cookie (`arccompass_sid`, plus `arccompass_sls` for the last-activity timestamp) so reloads stay within the same session;
- slides on a 30-minute inactivity window — the next `track()` after the window expires mints a fresh session ID.

`session_id` is sent on every event and is used server-side to group a visit’s interactions into one personalization session. A per-call override via `track(type, { sessionId })` is honored.

### Consent gating

There is a single consent gate (see above). There is no ephemeral-ID soft-gate: before consent is granted the SDK creates and reads no persistent identifier, and on denial any persistent ID is wiped. Once granted, events carry the stable identity (host-set ID or the anonymous `arccompass_aid`) and the SDK-managed session ID.

### Safari / ITP 7-day caveat

Both cookies set from JavaScript and `localStorage` entries are subject to Safari’s Intelligent Tracking Prevention 7-day cap on script-writeable storage. There is no client-only workaround, and this applies to the SDK’s `arccompass_aid`fallback. Returning visitors after seven days of no interaction may appear as new anonymous IDs, splitting their history. A stable **host-managed** ID passed to `setUserId()` (persisted server-side or in a first-party context you control) is the durable way to avoid this.

## See also

- [Compass Web SDK: Integration Guide](https://dev.arcxp.com/content-recommendations/sdk/integration-guide/)
- [Compass Web SDK: Events & Reliability](https://dev.arcxp.com/content-recommendations/sdk/events-reliability/)
