# Technical Implementation Guide

*Audience: engineers deploying and integrating the Relyance Consent Agent.*

## 1. Providing a login recording for authenticated scans

Do this first, before installing the agent, if any of your domains need to be scanned behind a login wall (a customer portal, an authenticated app shell, etc.). The Domain Analyzer can crawl those pages (see the [Admin Configuration Guide](/docs/consent-management/admin-configuration-guide/#scanning-behind-authentication)) - configuring it is entirely an admin task in the platform, but producing the **login recording** for form-based logins is a small, one-time engineering task:

1. Open Chrome DevTools → the **Recorder** panel.
2. Start a new recording and step through the actual login flow: navigate to the login page, enter credentials, submit, and land on an authenticated page.
3. Stop the recording and export it as JSON.
4. Hand the exported JSON file to the admin configuring the domain - they upload it in the Authentication step of the Add/Edit Domain wizard, where Relyance replays it to sign in before crawling.

Basic Auth (no recording needed) is also supported for domains protected only by an HTTP Basic auth prompt. Everything else here - selecting the auth method, entering credentials, testing, scan scheduling, and notifications - is configured by admins directly in the platform; no code changes are required. If none of your domains sit behind a login, skip ahead to installing the agent below.

## 2. Overview

The **Consent Agent** is a single JavaScript file your team embeds on every page of a domain. Once loaded, it:

- Looks up the current domain against your Relyance configuration and applies the correct consent rules automatically - **the script itself is identical across every domain you manage**; there's no per-domain build.
- Detects the visitor's region and determines which Region Group (and therefore which consent model, banner, and behavior) applies.
- Renders the consent banner, preference center, and related UI.
- Blocks or allows cookies, scripts, storage writes, and network requests based on the visitor's actual consent state.
- Emits events your own code can listen to, and exposes a small JS API for things like reopening the preference center.
- Can forward consent state into a third-party tag manager such as Google Tag Manager via a short integration script (see §14) - native, automatic dataLayer updates from the agent are planned for a future release.

Get your exact install snippet from **Domain Detail → Agent tab → Deploy the Consent Agent** in the Relyance platform - copy it directly rather than hardcoding a URL from documentation, since the exact script host can differ by environment and data-residency region (e.g., EU vs. US hosting).

## 3. Installation

Place the script tag in `<head>`, **as early as possible** - before any other tag manager, analytics, or third-party script. This is the single most important rule in this guide: if other scripts load before the agent, they can fire before consent is evaluated, defeating the purpose of the integration.

```html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>My Site</title>
    <script
      src="https://consent.app.relyance.ai/relyance-agent.js"
      data-relyance-consent-appId="YOUR_APP_ID"
      data-relyance-zero-fire-mode="true"
    ></script>
    <!-- all other scripts, tag managers, analytics, etc. go AFTER this -->
  </head>
  <body>...</body>
</html>
```

The agent initializes itself automatically on load - there's no manual `init()` call required.

### Next.js

A plain `<script>` tag can break in Next.js due to how SSR/hydration ordering works. Use `next/script` with `strategy="beforeInteractive"`, which guarantees the script is injected into `<head>` and executes before the page becomes interactive.

**App Router (`app/layout.tsx`):**
```tsx
import type { Metadata } from 'next'
import Script from 'next/script'

export const metadata: Metadata = {
  title: 'My Next.js App',
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Script
          data-relyance-consent-appId="<YOUR_APP_ID_HERE>"
          data-relyance-zero-fire-mode="true"
          src="https://consent.app.relyance.ai/relyance-agent.js"
          strategy="beforeInteractive"
        />
        {children}
      </body>
    </html>
  )
}
```

**Pages Router (`_app.tsx`):**
```tsx
import Script from 'next/script'

export default function MyApp({ Component, pageProps }) {
  return (
    <>
      <Script
        data-relyance-consent-appId="<YOUR_APP_ID_HERE>"
        data-relyance-zero-fire-mode="true"
        src="https://consent.app.relyance.ai/relyance-agent.js"
        strategy="beforeInteractive"
      />
      <Component {...pageProps} />
    </>
  )
}
```

**Verify the install:** open DevTools → Network tab, filter for "relyance", and confirm the agent script request appears; then check Elements → `<head>` to confirm the `<script>` tag is present with the expected `src`.

## 4. Configuration attributes

Set these as `data-*` attributes on the script tag.

| Attribute | Required | Purpose |
|---|---|---|
| `data-relyance-consent-appId` | **Yes** | Your app/domain identifier. The agent will not initialize without it. |
| `data-relyance-zero-fire-mode` | Recommended | Set to `"true"` to enable full pre-consent script/iframe blocking (see §6). Without it, the script/iframe interceptor is inactive - cookie, storage, and network-level protections still apply, but injected `<script>`/`<iframe>` tags are not blocked pre-consent. |
| `data-relyance-skip-inline` | No | Set to `"true"` to exclude inline `<script>` blocks (no `src` attribute) from interception - only external-`src` scripts are gated. Useful if inline scripts on your site are causing false-positive blocking. |
| `data-relyance-skip-url-patterns` | No | Semicolon-separated list of URL literals or `/regex/flags` patterns that should always be allowed through, regardless of consent state. See §13 for a real troubleshooting example. |
| `data-relyance-pattern-match` | No | Works alongside `data-relyance-skip-url-patterns` to enable regex-style pattern matching for that list (used together in the troubleshooting example in §13). If you rely on this heavily, confirm exact matching behavior with your Relyance contact. |
| `data-force-allow-new-technologies` | No | Controls what happens to a newly-discovered tracking technology that hasn't yet been mapped to a processing activity in Relyance. Set `"true"` to allow it through by default while your team categorizes it, rather than blocking by default. |

Query-string parameters (not script attributes) are also available for testing - see §12.

## 5. Cookies the agent sets

| Cookie name | Contents | Lifetime |
|---|---|---|
| `rly_consent` | The visitor's consent state - model, per-purpose consent, timestamp, whether it's a default (unconfirmed) consent | 365 days |
| `rly_device_id` | A randomly generated device identifier | 365 days |
| `rly_user` | Present once a user is authenticated - internal user ID + your `externalUserId` | Session-linked |

All are scoped to your configured domain (not the exact hostname), which is what allows consent to be shared across subdomains, `SameSite=Strict`, and marked `Secure` on HTTPS pages.

**Reading the consent cookie for debugging:**
```js
const raw = document.cookie
  .split('; ')
  .find(row => row.startsWith('rly_consent='))
  ?.split('=')[1];

const decoded = JSON.parse(atob(decodeURIComponent(raw)));
console.log(decoded);
```

Cookie payload structure may evolve between agent versions - treat the JS API and events (§7-8) as the stable, supported way to read consent state programmatically rather than parsing the cookie directly in production code.

## 6. How enforcement works

The agent enforces consent through several independent interceptors, all governed by the same underlying rule: **a tracking technology may only act if all the processing activities it's mapped to have been consented to** (or if it's marked strictly necessary).

| Mechanism | What it does |
|---|---|
| **Script blocking** | Compares each `<script src>` against the Resource URL Patterns configured for each tracking technology; blocks non-consented scripts from executing, unblocks them the moment consent is granted. Requires `data-relyance-zero-fire-mode="true"`. |
| **Iframe blocking** | Same mechanism, applied to `<iframe>` embeds. Also requires zero-fire mode. |
| **Cookie interceptor** | Intercepts JS calls that set cookies; blocks/deletes any tied to a non-consented processing activity. Active by default (no flag required). |
| **Local/session storage interceptor** | Intercepts `localStorage.setItem()` / `sessionStorage.setItem()`; blocks non-essential writes without consent. Active by default. |
| **Network interceptor** | Flags and manages HTTP requests to endpoints associated with non-consented tracking technologies. Active by default. |
| **Pixel interceptor** | Detects and blocks tracking-pixel image requests when consent is absent. |
| **Beacon interceptor** | Monitors and blocks `navigator.sendBeacon()` calls tied to non-consented processing activities. |

**Important:** correctly mapping *strictly necessary* tracking technologies to the right processing activity matters - if a functionally required script isn't mapped as strictly necessary, the agent can end up blocking something your site actually needs to function. Review this mapping in Tracking Technology Management (see the [Admin Configuration Guide](/docs/consent-management/admin-configuration-guide/)) before going live.

**Updating what a technology blocks/unblocks against:** each tracking technology has a **Resource URL Pattern** field (the URL pattern matching its script's `src`). To adjust it: open the tracking technology's edit dialog → update **Resource URL Patterns** → **Save** → **Publish** the configuration for it to take effect.

## 7. JavaScript API

`window.Relyance` exposes two things: a small set of convenience methods on the object itself, and a fuller `agent` instance (`Relyance.agent`) that carries the event system (§8), login/logout callbacks (§11), and the lower-level storage utility used for GTM integration (§14).

The convenience methods on `Relyance` itself:

```ts
interface RelyanceInterface {
  showPreferenceCenter(): Promise<void>;
  getPolicyData(): ConsentPolicyData | null;
  getPolicyHtml(): string;
  readonly isInitialized: boolean;
}
```

Always check `Relyance.isInitialized` (or listen for the `RELYANCE_AGENT_INITIALIZED` event) before calling into either `Relyance` or `Relyance.agent` from code that might run before the agent has loaded.

### Opening the preference center

```html
<!-- Footer link -->
<a href="#" onclick="Relyance.showPreferenceCenter(); return false;">Update Privacy Preferences</a>

<!-- Button -->
<button type="button" onclick="Relyance.showPreferenceCenter();">Manage Cookies</button>
```

Place this prominently (footer, privacy page, account settings) with a clear label ("Manage Preferences," "Cookie Settings"), and make sure it's keyboard-navigable with appropriate ARIA attributes - most jurisdictions expect visitors to be able to change their mind after the initial banner interaction.

### Rendering the policy page

The Policy page isn't hosted at a Relyance URL - it renders client-side into a container element on a page you host yourself (e.g., `/legal/cookie-policy`). Configure its content first (title, section copy, the auto-populated technology table) in [Interface Builder's Policy tab](/docs/consent-management/admin-configuration-guide/#policy-page), then embed it:

```html
<div id="rly-policy-container"></div>
```

```js
document.getElementById('rly-policy-container').innerHTML = Relyance.getPolicyHtml();
```

If you're in a single-page app, call this again after route changes recreate the container element. Prefer your own markup instead of the pre-built HTML? Call `Relyance.getPolicyData()` for the structured data instead of `getPolicyHtml()`.

### Login / logout (Authenticated Consent)

```js
// Immediately after successful login
Relyance.agent.onLoginCallBack({
  externalUserId: 'user@example.com', // or your internal unique user ID
});

// On logout
Relyance.agent.onLogoutCallBack();
```

See §11 for the full sync behavior. `onLogoutCallBack()` only clears local agent context on the current device - it does not notify the backend or affect the stored consent record.

## 8. Events

Register listeners on the agent instance:

```js
Relyance.agent.addEventListener('EVENT_NAME', (e) => {
  console.log(e);
});
```

| Event | Fires when | Payload |
|---|---|---|
| `RELYANCE_AGENT_INITIALIZED` | Initialization completes | - |
| `RELYANCE_LOCATION_FETCHED` | Visitor's region has been resolved | - |
| `RELYANCE_BEHAVIOR_CONFIGS_LOADED` | Region/behavior config has loaded | - |
| `RELYANCE_CONSENT_UPDATE` | Consent changes (Accept All, Reject All, or Confirm Choices) | `consentStatus`, keyed by processing activity, e.g. `{ PROCESSING_ACTIVITY_MARKETING: true }` |
| `RELYANCE_BANNER_RENDERED` | Banner is shown | - |
| `RELYANCE_BANNER_BUTTON_CLICKED` | A banner button is clicked | `bannerButton` |
| `RELYANCE_BANNER_CONTAINER_CLICKED` | Any click within the banner's outer container | - |
| `RELYANCE_BANNER_CLOSED` | Banner is dismissed | - |
| `RELYANCE_CLICK_OFF_BANNER` | Visitor clicks outside the banner | - |
| `RELYANCE_PREFERENCE_CENTER_BUTTON_CLICKED` | A preference-center button is clicked | `preferencecenterButton` |
| `RELYANCE_PREFERENCE_CENTER_CONTAINER_CLICKED` | Any click within the preference center | - |
| `RELYANCE_PREFERENCE_CENTER_PURPOSE_CLICKED` | A purpose toggle is switched | `purposeId`, `checked` |
| `RELYANCE_PREFERENCE_CENTER_CLOSED` | Preference center is closed | - |
| `RELYANCE_USER_REGISTRATION` / `RELYANCE_USER_VISIT` / `RELYANCE_USER_ASSOCIATION` | Authenticated-consent lifecycle events | - |
| `RELYANCE_USER_CONSENT_RECEIPT` | A consent receipt is recorded for a user | - |

**Most common pattern** - run code only when a specific purpose is consented:

```js
Relyance.agent.addEventListener('RELYANCE_CONSENT_UPDATE', (e) => {
  if (e.consentStatus.PROCESSING_ACTIVITY_MARKETING) {
    // initialize marketing pixel, etc.
  }
});
```

Interaction events (banner/preference-center clicks) are useful for analytics or A/B testing your consent UI itself - just make sure anything you do in response still respects the visitor's actual consent choice.

## 9. Global Privacy Control (GPC)

The agent checks `navigator.globalPrivacyControl` for an incoming GPC signal. Whether and how it's honored is controlled server-side, per Region Group (see the [Legal and Compliance Guide](/docs/consent-management/legal-and-compliance-guide/#global-privacy-control-gpc) and the [Admin Configuration Guide](/docs/consent-management/admin-configuration-guide/)) - there's nothing additional to implement client-side beyond the standard install. If GPC honoring is enabled and a signal is detected, non-exempt processing activities are automatically treated as rejected, and (if configured) a small floating confirmation widget is shown to the visitor confirming their signal was respected.

## 10. Content Security Policy (CSP)

If your site enforces a CSP, allowlist the agent's required hosts. As of this writing, the standard directives are:

```
script-src 'self' 'unsafe-inline' us-central1-relyance-ext.cloudfunctions.net consent.app.relyance.ai;
connect-src 'self' us-central1-relyance-ext.cloudfunctions.net consent.app.relyance.ai;
```

These hosts can differ if you're on a region-specific deployment (e.g., EU data residency) - confirm the exact hostnames with your Relyance contact if your CSP is strict and you're not on the standard US environment.

## 11. Authenticated Consent and cross-device sync

When a visitor logs in, calling `onLoginCallBack()` ties their consent to a stable identifier rather than just the browser/device, and reconciles any existing local consent with what's already stored for that user.

| Scenario | Behavior |
|---|---|
| New device, no local consent | Stored (server) consent is fetched and applied |
| Local consent exists, and a stored record exists | Stored consent overwrites local (assumed more recent) |
| First login, no stored record yet | Local consent is pushed and tied to the user ID |
| Consent changed while logged in | Synced immediately; reflected on other devices at next login/refresh |

Internally, the agent derives a privacy-preserving identifier from your app ID and the `externalUserId` you pass - it does not store your raw identifier directly as the primary key. If the network is unavailable at login, the agent falls back to the last known local cache; conflict resolution uses a "latest wins" rule, preferring the stored/global record when its timestamp is more recent than the local one. Every page reload for a logged-in visitor re-syncs their latest known consent.

**Choosing what to pass as `externalUserId`:** this value is stored for audit purposes in the consent log, so choose based on your own data-minimization posture - an internal UUID is generally preferable to a raw email address if you don't otherwise need the email visible in consent records.

## 12. Testing and QA

### Getting a test agent script

Before installing the production agent site-wide, get a dedicated **test agent script** from **Domain Detail → Testing tab** in the Relyance platform (separate from the production install snippet on the **Agent** tab). Use this test script to validate behavior on a staging environment or a subset of pages without affecting how the production agent behaves for live visitors. Once testing is complete, switch to the production script from the Agent tab for the actual deployment (§3).

### Conditional loading for isolated testing

You can also gate loading behind a query parameter, so a test script only loads for you rather than every visitor to a page:

```js
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('loadConsentAgent') === 'true') {
  const s = document.createElement('script');
  s.src = 'YOUR_TEST_AGENT_SCRIPT_URL'; // from Domain Detail → Testing tab
  s.onload = () => console.log('Consent Agent loaded successfully!');
  s.onerror = () => console.error('Failed to load Consent Agent script!');
  document.head.appendChild(s);
} else {
  console.log('Consent Agent not loaded (query parameter not present).');
}
```

Load the page normally to confirm the agent does *not* load, then load with `?loadConsentAgent=true` to confirm it does.

### Simulating a region

Append `?cm_region=<code>` to simulate a visitor location without VPN or physical travel:

```
https://example.com?cm_region=FR       # simulates an EU visitor
https://example.com?cm_region=US-CA    # simulates California (country + state)
```

The country or state you're simulating must already be covered by a configured Region Group, or the simulation won't map to meaningful behavior.

### Other useful test parameters

| Parameter | Effect |
|---|---|
| `?rly_clear_cookie=true` | Deletes the consent cookie on load - useful for repeatedly testing first-visit banner behavior |
| `?cm_test_domain=TRUE` | Runs against a test configuration, bypassing strict domain validation |
| `?rly_loc=...` | Overrides geolocation for testing |

### Debug logging

Off by default. Enable via DevTools console or Application/Storage tab:

```js
localStorage.setItem('consent-debug', 'true');
```

Reload the page. With debug mode on, the console logs agent initialization, interceptor setup, user interactions (banner/preference-center clicks), and consent-update events. Debug mode is local-browser-only - it never changes agent behavior for real visitors, only what's logged to your own console. Disable by setting the key to `'false'` or removing it from local storage.

## 13. Troubleshooting

**Domain scan returns 0 tracking technologies, or fails outright.** Your WAF, CDN bot protection (e.g., Cloudflare), or IPS/firewall is likely blocking the Relyance scanning crawler. Allowlist these IPs in your security tooling:

```
34.82.181.167
35.199.149.43
34.145.53.165
8.229.95.221
34.83.113.133
```

**A third-party script breaks after the agent is installed** (e.g., a UI library throws `X is not a function`, or a CDN-hosted dependency silently fails to initialize). This usually means the interceptor is neutralizing a script your site needs regardless of consent state. Two ways to fix it, in order of preference:

1. Map the affected tracking technology to a "strictly necessary" processing activity in Tracking Technology Management, so it's allowed regardless of consent choice (see the [Admin Configuration Guide](/docs/consent-management/admin-configuration-guide/)).
2. If it's not really a tracking technology at all (e.g., a UI framework CDN, not an analytics/marketing script), exclude its URL from interception entirely using `data-relyance-skip-url-patterns` on the script tag:

```html
<script
  src="https://consent.app.relyance.ai/relyance-agent.js"
  data-relyance-consent-appId="YOUR_APP_ID"
  data-relyance-zero-fire-mode="true"
  data-relyance-skip-inline="true"
  data-relyance-pattern-match="true"
  data-relyance-skip-url-patterns="/^\/(?!\/)|(\.your-cdn\.com)|(cdn\.jsdelivr\.net)/"
></script>
```

**The agent fails to initialize.** Confirm `data-relyance-consent-appId` is present and correct - the agent throws immediately and does nothing further if it's missing.

**Consent doesn't persist across subdomains.** Confirm the domain configured in Relyance for this property matches the parent domain you expect cookies to be scoped to (not a specific subdomain).

## 14. Google Tag Manager (GTM) integration

**This section is optional.** The agent already blocks non-consented scripts, cookies, storage, network requests, pixels, and beacons natively (§6) - you don't need a GTM integration for enforcement to work. This pattern is most relevant if you're **not** running with `data-relyance-zero-fire-mode="true"` (so script/iframe blocking isn't active at the interceptor level, §4) and want your tag manager itself to be consent-aware, or if your team simply prefers gating tags through GTM triggers rather than relying solely on the agent's native blocking. If you are using zero-fire mode, treat this as a nice-to-have rather than a requirement.

The agent does not yet push consent state into a tag manager's dataLayer automatically - that native integration is planned for a future release. Until then, forwarding consent to Google Tag Manager (or any dataLayer-driven tag manager) is a small integration you add yourself, using a utility already exposed by the agent. This section documents that pattern.

### Reading consent state programmatically

The agent exposes a lower-level storage utility, separate from the `window.Relyance` API described in §7, for reading the visitor's full consent object directly:

```js
const consentStatus = Relyance.agent.relyanceConsentStorageService.readConsentFromBrowser();
```

This returns the same underlying consent record described in §5 (the `rly_consent` cookie payload), but as a live JS object rather than something you decode yourself. A representative response:

```json
{
  "model": "CONSENT_MODEL_OPTIONS_OPT_IN",
  "consentPurposes": [
    {
      "purposeId": "PROCESSING_ACTIVITY_STRICTLY_NECESSARY",
      "name": "Strictly Necessary",
      "visible": true,
      "checked": true,
      "alwaysActive": true
    },
    {
      "purposeId": "PROCESSING_ACTIVITY_MARKETING",
      "name": "Marketing",
      "visible": true,
      "checked": false,
      "alwaysActive": false
    },
    {
      "purposeId": "69388d52666af3da8b53a9e8",
      "name": "Preferences",
      "visible": true,
      "checked": false,
      "alwaysActive": false
    },
    {
      "purposeId": "69388d72666af3da8b53a9ea",
      "name": "Analytics",
      "visible": true,
      "checked": false,
      "alwaysActive": false
    }
  ],
  "timestamp": 1781217123777,
  "consentStorageEnabled": true,
  "isDefaultConsent": true
}
```

| Field | Meaning |
|---|---|
| `purposeId` | Identifier for the consent category. Well-known categories use a `PROCESSING_ACTIVITY_*` constant (matching the keys used in the `RELYANCE_CONSENT_UPDATE` event, §8); custom categories use a MongoDB-style ObjectId string instead - map these to readable names yourself. |
| `name` | Human-readable category label, as configured in your Relyance account. |
| `checked` | `true` if the visitor has consented to this category, `false` if denied. |
| `alwaysActive` | `true` for categories (e.g., Strictly Necessary) that can't be toggled off. |
| `isDefaultConsent` | `true` means the visitor hasn't interacted with the banner yet - this is the state on first page load, before any explicit choice. |

Because custom categories are keyed by ObjectId rather than a named constant, you'll need to maintain a small map from those IDs to your own dataLayer key names - see the implementation script below.

### Pushing consent to the dataLayer

Add this script after the Relyance Consent Agent script tag. It pushes an initial `RelyanceConsentLoaded` event on load, then re-pushes on every consent change:

```html
<script>
  window.dataLayer = window.dataLayer || [];

  // Map purposeId values to friendly dataLayer key names.
  // Update this to match your Relyance account's processing activities,
  // including any custom (ObjectId-keyed) categories.
  var RELYANCE_PURPOSE_MAP = {
    'PROCESSING_ACTIVITY_STRICTLY_NECESSARY': 'relyance_consent_necessary',
    'PROCESSING_ACTIVITY_MARKETING': 'relyance_consent_marketing',
    '69388d52666af3da8b53a9e8': 'relyance_consent_preferences',
    '69388d72666af3da8b53a9ea': 'relyance_consent_analytics',
  };

  function pushRelyanceConsentToDataLayer() {
    try {
      var response = Relyance.agent.relyanceConsentStorageService.readConsentFromBrowser();
      if (!response || !response.consentPurposes) return;

      var payload = { event: 'RelyanceConsentLoaded' };

      response.consentPurposes.forEach(function (purpose) {
        var key = RELYANCE_PURPOSE_MAP[purpose.purposeId];
        if (key) {
          payload[key] = purpose.checked || false;
        }
      });

      payload.relyance_consent_model = response.model;
      payload.relyance_is_default_consent = response.isDefaultConsent;

      window.dataLayer.push(payload);
    } catch (e) {
      console.warn('Relyance consent read failed:', e);
    }
  }

  // Fire on initial page load
  pushRelyanceConsentToDataLayer();

  // Re-push whenever the visitor changes their consent
  Relyance.agent.addEventListener('RELYANCE_CONSENT_UPDATE', function () {
    pushRelyanceConsentToDataLayer();
    window.dataLayer.push({ event: 'RelyanceConsentUpdated' });
  });
</script>
```

The `try/catch` matters here: if this script runs before the agent has finished initializing, `Relyance.agent` may not exist yet. For a more deterministic first push, gate the initial call on `Relyance.isInitialized` or the `RELYANCE_AGENT_INITIALIZED` event (§7-8) instead of relying solely on the catch block.

### Setting up GTM

**1. Create a Data Layer Variable per consent category** (Variables → User-Defined Variables → New → Data Layer Variable), named to match each key from the map above:

| GTM variable name | Data layer key |
|---|---|
| DLV - Relyance Consent Necessary | `relyance_consent_necessary` |
| DLV - Relyance Consent Marketing | `relyance_consent_marketing` |
| DLV - Relyance Consent Preferences | `relyance_consent_preferences` |
| DLV - Relyance Consent Analytics | `relyance_consent_analytics` |
| DLV - Relyance Is Default Consent | `relyance_is_default_consent` |

**2. Create an Active and a Blocking trigger per category.** Both are Custom Event triggers matching `RelyanceConsentLoaded|RelyanceConsentUpdated` with regex matching enabled:
- **Active** (e.g., "Consent Active - Analytics"): fires when `DLV - Relyance Consent Analytics` equals `true`.
- **Blocking** (e.g., "Consent Blocked - Analytics"): fires when it does not equal `true`.

The Strictly Necessary category typically doesn't need this pattern, since it's always `true`.

**3. Apply the triggers to each tag.** On the tag you want to gate (e.g., a GA4 Configuration tag), add the Active trigger under Triggering, then add the matching Blocking trigger as an **exception**. The tag will only fire once the required consent is `true`, and will stop firing again if consent is later revoked.

**Suggested category-to-tag mapping:**

| Consent category | Typical tag types | dataLayer key |
|---|---|---|
| Strictly Necessary | Security, authentication, load balancing - no blocking needed | `relyance_consent_necessary` |
| Marketing | Google Ads, Meta Pixel, LinkedIn Insight, Bing Ads, TikTok Pixel | `relyance_consent_marketing` |
| Preferences | Chat widgets, A/B testing, language/region preferences | `relyance_consent_preferences` |
| Analytics | GA4, Adobe Analytics, Hotjar, Heap | `relyance_consent_analytics` |

### Handling mid-session consent changes

When a visitor updates their choices through the preference center, the agent dispatches `RELYANCE_CONSENT_UPDATE` (§8). The script above listens for it and pushes a `RelyanceConsentUpdated` event with the refreshed flags - so make sure every trigger's event-name regex includes both `RelyanceConsentLoaded` and `RelyanceConsentUpdated`, or tags won't re-evaluate when a visitor changes their mind mid-session.

### Default-deny state before the agent loads

Tags can otherwise fire during the brief window before the agent (and this script) has run. Push an explicit deny-by-default state at the very top of `<head>`, before both the GTM snippet and the Relyance agent script:

```html
<script>
  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push({
    event: 'RelyanceConsentLoaded',
    relyance_consent_necessary: true,     // Always allowed
    relyance_consent_marketing: false,    // Denied by default
    relyance_consent_preferences: false,  // Denied by default
    relyance_consent_analytics: false,    // Denied by default
    relyance_is_default_consent: true,    // No explicit choice yet
  });
</script>
<!-- GTM script tag follows here -->
```

Defaulting every non-essential category to `false` means no tracking occurs until a visitor has explicitly consented - the recommended posture under GDPR and other opt-in regulations (see the [Legal and Compliance Guide](/docs/consent-management/legal-and-compliance-guide/#the-four-consent-models)).

### Testing the integration

- **GTM Preview mode:** load your site with Preview active, select the `RelyanceConsentLoaded` event in the debug panel, and confirm each `DLV - Relyance Consent *` variable shows the expected value, and that gated tags appear under Tags Fired or Tags Not Fired as expected.
- **Mid-session update:** with Preview still active, open the preference center, change a category, and confirm a `RelyanceConsentUpdated` event appears with the updated variable value and the relevant tags re-firing or getting blocked.
- **Browser console:** inspect pushed events directly with `window.dataLayer.filter(e => e.event && e.event.startsWith('Relyance'))`, and cross-check against the raw object from `Relyance.agent.relyanceConsentStorageService.readConsentFromBrowser()`.

### GTM integration troubleshooting

| Symptom | Likely cause / fix |
|---|---|
| `readConsentFromBrowser()` returns `undefined` | The agent hasn't finished loading yet. Confirm the Relyance script tag loads before your dataLayer push script, and gate the call on `RELYANCE_AGENT_INITIALIZED` if it's still unreliable. |
| GTM Data Layer Variables are empty | Variable names are case-sensitive - confirm they exactly match the keys pushed by your script. Inspect the `RelyanceConsentLoaded` event's Variables tab in Preview mode. |
| Tags fire before consent is given | Add the default-deny push above, before the GTM snippet in `<head>`. |
| Tags don't re-fire after a consent update | Confirm your trigger's event-name regex includes `RelyanceConsentUpdated`, and that `RELYANCE_CONSENT_UPDATE` is actually firing on preference save (log it directly: `Relyance.agent.addEventListener('RELYANCE_CONSENT_UPDATE', console.log)`). |
| Tags never fire, even after consent is granted | Check that the `purposeId` values in `RELYANCE_PURPOSE_MAP` match your account's actual processing activities - log the raw consent object and cross-check the IDs. |

## Related reading

- [Overview and Implementation Guide](/docs/consent-management/overview-and-implementation-guide/) - where this fits in the overall rollout
- [Admin Configuration Guide](/docs/consent-management/admin-configuration-guide/) - configuring the platform side (Region Groups, Tracking Technology Management, Interface Builder, Domain Analyzer scheduling and authentication)
- [Consent Logs and Reporting](/docs/consent-management/consent-logs-and-reporting/) - what gets logged and how to export it
