Google Tag Manager installation

View as Markdown

There are two ways to install Telltale with Google Tag Manager:

Use one of them per site, never both.

GTM steps checked against the official Google Tag Manager documentation on 2026-11-03: Custom HTML tags, Consent mode and tag consent settings, Using Tag Manager with a Content Security Policy, Preview and debug containers.

The Telltale template

Download the .tpl from Connect a site → Download GTM template. The generated file contains the selected site's public key and an exact script-injection permission for this collector's /sdk.js. It contains no server key. GTM requires HTTPS even for imported script permissions. Configure PUBLIC_ORIGIN (or the public edge) to an exact trusted HTTPS collector origin. An HTTP collector returns a clear 400 error instead of an invalid template.

Before downloading, list any custom consent types your CMP sets (comma-separated, for example telltale_measurement). The template always grants read access to Google's standard types (ad_storage, analytics_storage, ad_user_data, ad_personalization, functionality_storage, personalization_storage, security_storage); each custom type you list gets its own read row in the access_consent permission. For a template you already imported, add the custom type under the template's Permissions → Accesses consent state instead. The template never writes consent state.

Choose a collection mode

The tag has a Let my CMP control collection checkbox, off by default (changed 2026-10-28): like the hosted script tag, the tag starts recording on page load. Turn it on if a consent banner must decide first; EU and UK sites usually need one. A refusal already saved on the browser is honoured in both modes.

Existing containers. A container saved with the box checked keeps it checked and stays held until the CMP decides. A tag imported from an earlier template without a saved value for the box now starts on load; open the tag and check the box if your site relies on a consent banner.

Use one mode per site and page: a held and an ordinary tag for the same site conflict and the tag fails instead of silently overriding.

Set up CMP control

  1. Import the file in a GTM Web container: Templates → New → menu → Import. Save it and create a tag using that template.
  2. In the CMP's Consent Initialization tag, set denied defaults for every selected consent type. Google treats an unset type as granted, so a missing default is not safe. This template does not set defaults or replace the CMP.
  3. Enable GTM's built-in Event variable and create two Custom JavaScript variables that return real booleans (not the string "true"):
JavaScript
// "Telltale consent decision event"
function () { return {{Event}} === 'telltale_consent_decision'; }
// "Telltale identity ready event"
function () { return {{Event}} === 'telltale_identity_ready'; }
  1. Map Apply current CMP decision to the consent-decision variable, CMP decision is granted to your CMP's aggregate result for the purposes this deployment requires, and Identity is current for this event to the identity-ready variable.
  2. Fire the tag once on every page after Consent Initialization, even while denied, so the withdrawal listeners are installed, and again on the telltale_consent_decision and telltale_identity_ready custom events. Do not use an Additional Consent Check or an "always fire" override that would block or bypass this bridge.
  3. After the CMP has updated Google consent state, push the decision event for every saved decision on each page load and for every change, including a refusal that stays a refusal:
JavaScript
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ event: 'telltale_consent_decision' });
  1. Validate with GTM Preview/Tag Assistant, then publish through your normal GTM process. Test fresh load, saved refusal, grant, withdrawal, regrant, delayed script loading, logout and account switching.

Identity

Map User ID and Account ID to variables holding the current user's and account's stable internal IDs (prefer opaque IDs). If your container has no such variables, push both values with one event after authentication is ready, and on every login, logout and account switch:

JavaScript
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
  event: 'telltale_identity_ready',
  telltale_user_id: currentUser ? currentUser.id : null,
  telltale_account_id: currentAccount ? currentAccount.id : null
});

Create Data Layer Variables for telltale_user_id and telltale_account_id, map them to the tag's fields, and fire the tag on telltale_identity_ready with Identity is current for this event true (step 3). Push null values after logout. A changed user or account resets the local Telltale session before the replacement is associated.

Identity is event-scoped: it is delivered only on the identity event while collection is permitted, discarded while held, and never replayed after a withdrawal. After a regrant, fire a fresh identity event from your auth owner. These IDs are browser-supplied context, not verification; protected actions still need backend authentication and POST /api/v1/verify. No session token is ever written to the data layer or a tag variable.

Custom HTML tag

A Custom HTML tag runs a few lines of your own JavaScript. Settings › Channels in the Telltale dashboard shows the plain tag with your real key filled in:

HTML
<script src="https://app.trytelltale.dev/sdk/v1.js" data-telltale-key="YOUR_SITE_PUBLISHABLE_KEY"></script>

Do not paste that line into GTM as it is: GTM does not reliably keep data- attributes on elements in Custom HTML. The code below adds the same tag with JavaScript instead, so the attribute stays. It is written in older JavaScript (var, function) so any container accepts it.

  1. In GTM, go to Tags › New.
  2. Choose Tag Configuration › Custom HTML, and paste:
HTML
<script>
(function () {
  var SDK = 'https://app.trytelltale.dev/sdk/v1.js';
  if (window.Telltale || document.querySelector('script[src="' + SDK + '"]')) return; // already added
  var script = document.createElement('script');
  script.src = SDK;
  script.setAttribute('data-telltale-key', 'YOUR_SITE_PUBLISHABLE_KEY');
  document.head.appendChild(script);
})();
</script>
  1. Choose Triggering › All Pages. (Initialization - All Pages also works and fires a little earlier. Do not use Consent Initialization - All Pages; that trigger is for setting consent defaults.)
  2. Name the tag (for example "Telltale") and choose Save.
  3. Check it in Preview (see Check it worked), then choose Submit › Publish.

If your site shows a consent banner, let GTM's consent settings hold the tag until measurement is allowed. The tag then adds the SDK held and calls optIn(), so a saved refusal from an earlier visit is replaced only by a new grant. A held tag sends nothing until optIn().

  1. Use this code in the Custom HTML tag instead:
HTML
<script>
(function () {
  var SDK = 'https://app.trytelltale.dev/sdk/v1.js';
  function grant() {
    try { window.Telltale.getClient().optIn(); } catch (e) {}
  }
  if (window.Telltale) return grant(); // already loaded: a new grant
  if (document.querySelector('script[src="' + SDK + '"]')) return; // still loading: its onload grants
  var script = document.createElement('script');
  script.src = SDK;
  script.setAttribute('data-telltale-key', 'YOUR_SITE_PUBLISHABLE_KEY');
  script.setAttribute('data-telltale-wait-for-consent', '');
  script.onload = grant;
  document.head.appendChild(script);
})();
</script>
  1. Open Advanced Settings › Consent Settings. Under Additional Consent Checks, choose Require additional consent for tag to fire, then + Add required consent and enter analytics_storage (or the consent type your banner uses for this kind of measurement).
  2. Under Triggering, add All Pages and also a Custom Event trigger for the event your consent banner pushes after the visitor makes or changes a choice. GTM checks consent at the moment a trigger fires, so without the second trigger the tag would wait until the next page.
  3. Add a second Custom HTML tag, "Telltale withdraw", for when the visitor withdraws. Set its consent to No additional consent required, trigger it on the same consent-change event with a condition that matches a refusal (your banner's own data layer value), and paste:
HTML
<script>
(function () {
  try { if (window.Telltale) window.Telltale.getClient().optOut(); } catch (e) {}
})();
</script>
  1. Make sure your banner sets denied defaults on Consent Initialization - All Pages, as for the template.
  2. Save, check in Preview, then Submit › Publish.

For a consent manager that already has a recipe (OneTrust, Cookiebot, Sourcepoint), the template's Let my CMP control collection is the more thorough option: it listens for withdrawal itself. See also the Consent bridge guide.

Identify the signed-in user

Send opaque, stable internal IDs for the user and the account. Never send e-mail addresses, names or phone numbers. The recommended way is from your server after sign-in (Quickstart step 2, Server API › Associate identity); the data layer gives a browser label on top.

  1. When your auth knows who is signed in, and on every login, logout and account switch, push the IDs to the data layer:
JavaScript
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
  event: 'telltale_identity_ready',
  telltale_user_id: currentUser ? currentUser.id : null,
  telltale_account_id: currentAccount ? currentAccount.id : null
});
  1. In GTM, create two Data Layer Variable variables, "DLV - telltale_user_id" and "DLV - telltale_account_id", for those two keys.
  2. Add a Custom HTML tag, "Telltale identify", triggered by a Custom Event trigger for telltale_identity_ready, with the same consent settings as the main tag, and paste:
HTML
<script>
(function () {
  var userId = {{DLV - telltale_user_id}} || null;
  var accountId = {{DLV - telltale_account_id}} || null;
  function apply() {
    try {
      var client = window.Telltale.getClient();
      var done = userId || accountId
        ? client.identify({ userId: userId ? String(userId) : null, accountId: accountId ? String(accountId) : null })
        : client.reset(); // pushed with nulls: the user logged out
      done['catch'](function () {}); // rejects while held for consent or when the Telltale server is unreachable
    } catch (e) {}
  }
  if (window.Telltale) return apply();
  var tag = document.querySelector('script[src$="/sdk/v1.js"]');
  if (tag) tag.addEventListener('load', apply);
})();
</script>

Push null values only on logout, not on every page for visitors who are not signed in: each reset() starts a fresh session.

Content Security Policy

If your site sends a Content Security Policy, the Telltale server must be allowed for scripts and connections, whichever way you install:

HTTP
Content-Security-Policy: script-src 'nonce-RANDOM_PER_RESPONSE' 'strict-dynamic'; connect-src 'self' https://app.trytelltale.dev

Use Google's nonce-aware container snippet with the same nonce: Tag Manager then passes the nonce to the scripts it adds, and 'strict-dynamic' trusts the SDK script they add in turn. Your policy also needs the sources Google lists for Tag Manager itself. Never add 'unsafe-inline' or 'unsafe-eval' for Telltale.

GTM's Custom JavaScript variables only work with 'unsafe-eval', so under a strict policy the two Custom JavaScript variables in Set up CMP control step 3 do not evaluate. The Custom HTML path above uses only Data Layer Variables. The Content Security Policy guide has the full options.

Check it worked

  1. In GTM, choose Preview, enter your site's address and connect. In Tag Assistant, open the first page view and check that your Telltale tag is under Tags Fired. With a consent banner, it should fire only after you accept.
  2. Publish the container: Submit › Publish. Preview only affects your own browser.
  3. In the Telltale dashboard, open Settings › Channels and leave the Is it working? check open. Choose Open my site to test it and browse a page or two.
  4. Watch the check move through Script loaded on your address, First session received and, if you set up identity, Signed-in user linked. When the first session arrives, it links to it.

In your browser's DevTools › Network, sdk/v1.js should answer 200, and so should api/sdk/init. If it stays at the first step, see It's not working.

Common mistakes

Have an agent install this

Give a coding or browser agent this prompt, filled in with your container and CMP details:

Text
Install the Telltale Google Tag Manager template for this site. Use the site-specific template downloaded from the Telltale dashboard (Connect a site > Download GTM template) and the official GTM documentation. Inspect the GTM Web container first: find the CMP's Consent Initialization tag, the consent types it sets, and existing variables that expose the current signed-in user's stable ID and the current account, organization or workspace ID. Check "Let my CMP control collection" (it is off by default) when the site has a CMP. Select every consent type the CMP requires for this vendor; if one is a custom type, add a read permission row for it. Make sure denied defaults are set for each selected type on Consent Initialization. Create the {{Event}}-based Custom JavaScript variables for telltale_consent_decision and telltale_identity_ready, and add the documented dataLayer pushes only where the site does not already emit an equivalent event. Map the identity variables; if none exist, add the telltale_identity_ready push and Data Layer Variables. Never put a server key, session token or credential in GTM. Verify in Tag Assistant: held start with no collector requests, grant, withdrawal, regrant, logout and account switch. Stop before accepting any terms, submitting to the Community Template Gallery or publishing a production container, and report what you verified.

Loading behaviour

Repeated firings share one loader scope and use a cached script injection. The latest permission state wins if it changes during download. Duplicate script-load callbacks do not replay identity. An incompatible existing configuration for the same site fails instead of silently overriding it. Multiple sites keep separate managed owners and identity cancellation. Use one installation owner per site and page; don't combine this tag with another auth/consent synchronizer for the same SDK.

Remove the tag and publish to stop future installation. Withdrawal stops owned runtime work, but it cannot recall already accepted data. Historical data deletion is a separate operation.

Permissions and fail-closed checks

The template declares four permissions: inject_script for exactly <collector>/sdk.js, access_globals to execute only Telltale.installManaged, access_consent to read (never write) the standard Google types plus any custom types listed at download, and access_template_storage. Before it touches storage, consent or the page, the sandbox calls queryPermission for each of them, including a read check for every selected consent type, and validates the public key (ao_pk_ plus A–Z a–z 0–9 _ -, at most 128 characters) and, on identity events, the 128-character identity limit. Any failure calls gtmOnFailure and does nothing else, so permission drift (or a custom type without its permission row) fails loudly instead of half-installing. The managed installer returns true when it takes ownership of the callbacks; if the SDK global is missing, the tag fails instead of hanging.

Template tests

Every generated .tpl carries a ___TESTS___ block (default start on load, held start, grant with identity, script-load failure, withdrawal during load, duplicate loads, each permission query, denied permission, missing installer, ordinary start without a CMP, every selected consent type listened to and required, delayed-load consent reread, a custom type without its permission row, over-long identity). Run them with Run Tests in the template editor, then check the tag in GTM Preview before you publish.

Attribution: the terms header, test-block structure, permission pre-checks, key alphabet check, field conventions and metadata shape are adapted from an Apache-2.0 licensed GTM template; its licence notice ships with Telltale. The consent bridge is Telltale's own.