Medallia Digital Experience Analytics (DXA) from GTM

Implementing Medallia Digital Experience Analytics (DXA) in GTM — A Sandboxed JavaScript Recipe

Published · Tags: GTM, Medallia, DXA, Decibel Insight, Sandboxed JavaScript, Custom Template, Consent Management, CSP

Medallia Digital Experience Analytics — DXA, formerly known as Decibel Insight — ships its standard installation instructions as a Custom HTML snippet: a pair of dns-prefetch links followed by an inline <script> block that builds a queue object on window and injects the Decibel loader. That works, but every Custom HTML tag with inline JavaScript is a Content Security Policy liability, and a Custom HTML tag has no concept of consent state once the page has loaded. Hardcoded client and instance IDs make it painful to route production and development traffic to different DXA instances.

I would prefer to deploy as little custom JavaScript as possible to achieve the fastest, cleanest web measurement implementation possible. This post walks through the GTM Sandboxed JavaScript template I built to deploy DXA, the companion template that makes DXA "blind and mute" when consent is denied, and the full GTM Tag Template (.tpl) source — parameters, permissions, and all — that ties it together.

The Standard Decibel Insight Installation Script

Medallia's own documentation recommends installing DXA with the following markup: two dns-prefetch hints to warm the connection to Medallia's CDN, followed by an inline script that defines an error-tracking array, a command queue, and asynchronously injects the Decibel loader script.

<link rel="dns-prefetch" href="//cdn.decibelinsight.net">
<link rel="dns-prefetch" href="//collection.decibelinsight.net">
<script type="text/javascript">
// <![CDATA[
(function(d,e,c,i,b,el,it) {
  d._da_=d._da_||[];_da_.oldErr=d.onerror;_da_.err=[];
  d.onerror=function(){_da_.err.push(arguments);_da_.oldErr&&_da_.oldErr.apply(d,Array.prototype.slice.call(arguments));};
  d.DecibelInsight=b;d[b]=d[b]||function(){(d[b].q=d[b].q||[]).push(arguments);};
  el=e.createElement(c),it=e.getElementsByTagName(c)[0];el.async=1;el.src=i;it.parentNode.insertBefore(el,it);
})(window,document,'script','https://cdn.decibelinsight.net/i/99999/1111111/di.js','decibelInsight');
// ]]>
</script>

This is the snippet a Custom HTML tag would need to reproduce verbatim, IDs and all. It works, but it means a new SHA-256 hash in your CSP every time the script changes, and the client ID / instance ID (the two integers embedded in the loader URL) are hardcoded directly into markup rather than driven by GTM variables.

Why Build a Sandboxed JavaScript Template Instead

This may look like a lot of additional structure for what is functionally the same outcome, but it is just doing the above with a Sandboxed JavaScript Custom Template instead of a Custom HTML tag. The sandboxed template runs inside GTM's own permission-gated runtime rather than injecting a raw <script> block into the DOM, which means:

  • The client ID, instance ID, and CDN prefix become typed template parameters instead of hardcoded string literals, so the same template can be reused across properties and environments.
  • GTM's injectScript API requests permission for a specific URL allow-list (access_globals, inject_script) rather than relying on broad unsafe-inline CSP allowances.
  • The template's success/failure callbacks (data.gtmOnSuccess / data.gtmOnFailure) let downstream tags sequence off of DXA actually loading, which a fire-and-forget Custom HTML tag cannot do.
const log = require('logToConsole');
const injectScript = require('injectScript');
const createQueue = require('createQueue');
const setInWindow = require('setInWindow');
const copyFromWindow = require('copyFromWindow');

// Get template parameters
const decibelClientId = data.decibelClientId;
const decibelInstanceId = data.decibelInstanceId;
const decibelScriptPrefix = data.decibelScriptPrefix;

// Build script URL
const decibelScriptUrl = decibelScriptPrefix + decibelClientId + '/' + decibelInstanceId + '/di.js';

// Initialize Decibel Insight
const initializeDecibelInsight = () => {
  // Set up error tracking array
  const copyFromWindow = require('copyFromWindow');
  const existingDa = copyFromWindow('_da_');
  if (!existingDa) {
    setInWindow('_da_', []);
    setInWindow('_da_.err', []);
  }
  // Create DecibelInsight queue
  const decibelInsightQueue = createQueue('decibelInsight');
  setInWindow('DecibelInsight', 'decibelInsight');
  log('Medallia DXA: Initialized DecibelInsight queue');
};

// Initialize the tracking
initializeDecibelInsight();

// Inject the Decibel Insight script
injectScript(decibelScriptUrl, () => {
  log('Medallia DXA: Script loaded successfully');
  data.gtmOnSuccess();
}, () => {
  log('Medallia DXA: Failed to load script');
  data.gtmOnFailure();
}, 'decibelInsight');

The trigger for this tag is the "regular" initialization trigger — the one that fires after your consent initialization trigger, not before it. DXA should never load ahead of your consent gate.

Template Parameters

The published GTM Tag Template wraps the script above with three typed, validated parameters so the template can be reused without editing code per-property:

ParameterTypeValidationNotes
decibelClientIdTextNon-empty, positive numberThe 1st integer in the DXA loader URL.
decibelInstanceIdTextNon-empty, positive numberThe 2nd integer in the DXA loader URL.
decibelScriptPrefixTextNon-empty, regex ^https\:\/\/.*decibel.*\/$Defaults to https://cdn.decibelinsight.net/i/; the regex keeps the field constrained to a Decibel/Medallia CDN host.

Now that the template takes parameters, you can drive decibelInstanceId from a Lookup Table keyed on hostname (or any other criteria) if you want to vary the DXA instance for production and development without maintaining two separate tags.

Required Web Permissions

Because the template runs inside GTM's sandbox, it has to explicitly request every capability it uses. These are the permissions declared in the .tpl file's ___WEB_PERMISSIONS___ block:

APIScope granted
loggingConsole logging restricted to the debug environment only.
inject_scriptPermission to inject a <script> element whose source begins with https://cdn.decibelinsight.net/.
access_globalsRead / write / execute access to four specific window keys: _da_, _da_.err, DecibelInsight, and decibelInsight.

That last permission is the important one for the consent-blocking template below: GTM will refuse to let a sandboxed template touch window.decibelInsight at all unless access_globals explicitly lists it with read/write/execute access.

The template ships with a small base64-encoded brand icon and the description "Formerly known as Decibel by Medallia and Medallia DXA and Decibel Insights" — useful context if you're hunting through old documentation that uses any of those three names interchangeably.

Consent Compliance: Making DXA Blind and Mute

But what if you have a React JS single-page application? For that matter, what if consent is denied after DXA has already loaded and is mid-session, rather than before the next page load? How do you keep a JavaScript API like this consent-compliant when it is designed to keep running once injected?

That requires a second GTM Sandboxed JavaScript template. This script doesn't try to stop DXA from existing — it neuters it in place, using multiple techniques at once: replacing its public tracking methods with no-ops, flipping its internal opt-out flag, and finally nulling the global reference itself.

const createQueue = require('createQueue');
const setInWindow = require('setInWindow');
const copyFromWindow = require('copyFromWindow');

const decibelInsight = copyFromWindow('decibelInsight');
const decibelInsight_initiated = copyFromWindow('decibelInsight_initiated');

if (typeof decibelInsight !== 'undefined' && typeof decibelInsight_initiated !== 'undefined' && decibelInsight_initiated && decibelInsight) {
  setInWindow('decibelInsight.track', function() {}, true);
  setInWindow('decibelInsight.sendEvent', function() {}, true);
  setInWindow('decibelInsight.optOut', true, true);
  setInWindow('decibelInsight', null, true);
}

// Call data.gtmOnSuccess when the tag is finished
data.gtmOnSuccess();

This tag is triggered without parameters, based on the event that fires when a visitor clicks to deny consent. That click handler should additionally check the status of your relevant consent category for "denied" before this tag is allowed to run. How you wire that condition depends heavily on which consent management platform you're running.

Re-enabling DXA after a visitor later grants consent isn't a matter of "undoing" this script — once decibelInsight has been nulled out, the only path back is to reinitialize from scratch using the first template above.

What's on GitHub

The base initialization template — parameters, permissions, and sandboxed JavaScript exactly as shown above — is published as an importable GTM Tag Template .tpl file on GitHub:

FileContents
Medallia Digital Experience Analytics (DXA).tplThe full GTM Custom Template definition: metadata/brand block, the three decibelClientId / decibelInstanceId / decibelScriptPrefix parameters, the sandboxed JavaScript body, and the logging / inject_script / access_globals permission grants.
README.mdNotes that the template is intended for use when your existing Custom HTML tag matches Medallia's standard <script type="text/javascript">...</script> installation snippet — i.e., it's a drop-in replacement, not a different integration approach.

The repository ships only the base loader template; the consent "blind and mute" script is currently documented here and on the original Weebly post rather than packaged as its own .tpl. If you adapt it into an importable template, a pull request against the repository would make it easier for others to reuse.

How to Install

  1. Download Medallia Digital Experience Analytics (DXA).tpl from the GitHub repository.
  2. In GTM, go to Templates → New → Import (the overflow menu in the Custom Template editor) and select the .tpl file.
  3. Create a new tag from the imported template and fill in decibelClientId and decibelInstanceId from your Medallia-issued loader URL. Leave decibelScriptPrefix at its default unless Medallia has issued you a different CDN host.
  4. Set the trigger to your "regular" / post-consent-initialization trigger — never the container-load trigger directly, and never ahead of your consent gate.
  5. If you need different DXA instances per environment, swap the static decibelInstanceId value for a Lookup Table variable keyed on hostname.
  6. Build the consent-denial template using the second script above, wire it to your consent platform's "deny" event, and confirm in GTM Preview that decibelInsight.track and decibelInsight.sendEvent become no-ops immediately after a simulated opt-out.
  7. Publish, then verify in your network panel that di.js loads from cdn.decibelinsight.net only after consent has been granted.

This post is a Google Blogger adaptation of my original "Implementing Medallia Digital Analytics DXA from GTM", first published on my Weebly blog (DREW SPENCER) on January 16, 2026. The GTM Tag Template source — parameters, permissions, and sandboxed JavaScript — is published on GitHub at drewspen/Medallia-Digital-Experience-Analytics-DXA. Medallia Digital Experience Analytics (DXA), Decibel Insight, and Decibel by Medallia refer to the same Medallia product under its current and former names.

Comments

Popular posts from this blog

Site Landing & Site Referrer Preservation

GTM Browser Viewport Measurement

UTM & URL Query String 2 Cookies