Skip to content

Fetch External Data in GTM

Enriching GA4 events with data from external APIs can reveal patterns invisible to standard tracking. A geolocation API can tell you the user’s city without using GA4’s sampling. A CRM lookup can add account tier or lead score to conversion events. A product API can add inventory status to view_item events.

GTM Custom HTML tags execute synchronously by default. To make an API call and push the result to the dataLayer, you need the async/await pattern or promise-based approach, and you need to push to the dataLayer after the API response arrives.

dataLayer.push() geo_enriched

Enrich a page view event with geolocation data from an external API.

(function() {
// Use a lightweight, privacy-respecting geolocation API
// Replace with your preferred provider
var GEO_API = 'https://ipapi.co/json/';
// Check if we have cached data (avoid redundant API calls)
var cached = sessionStorage.getItem('geo_enrichment');
if (cached) {
try {
var geoData = JSON.parse(cached);
pushEnrichedEvent(geoData);
return;
} catch(e) { /* continue to API call */ }
}
fetch(GEO_API, { method: 'GET' })
.then(function(response) {
if (!response.ok) throw new Error('Geo API error: ' + response.status);
return response.json();
})
.then(function(data) {
var geoData = {
user_city: data.city || undefined,
user_region: data.region || undefined,
user_country: data.country_code || undefined,
user_timezone: data.timezone || undefined
};
// Cache for this session
sessionStorage.setItem('geo_enrichment', JSON.stringify(geoData));
pushEnrichedEvent(geoData);
})
.catch(function(error) {
console.warn('[GTM] Geo enrichment failed:', error.message);
// Push without enrichment — do not block the event
pushEnrichedEvent({});
});
function pushEnrichedEvent(geoData) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push(Object.assign({
event: 'geo_enriched',
page_path: window.location.pathname
}, geoData));
}
})();

If your site has an API that returns account/user data for authenticated users, enrich conversion events with that data:

(function() {
// Only enrich for authenticated users
var userId = {{DLV - user_id}};
if (!userId) return;
var cached = sessionStorage.getItem('crm_data_' + userId);
if (cached) {
enrichConversions(JSON.parse(cached));
return;
}
fetch('/api/user/' + userId + '/analytics-metadata', {
headers: { 'X-Analytics-Token': 'your-read-only-token' }
})
.then(function(r) { return r.json(); })
.then(function(data) {
var crmData = {
account_tier: data.plan || 'unknown',
account_mrr: data.mrr || 0,
days_since_signup: data.daysSinceSignup || 0,
has_churned_before: data.hasChurnedBefore || false
};
sessionStorage.setItem('crm_data_' + userId, JSON.stringify(crmData));
enrichConversions(crmData);
})
.catch(function(e) {
console.warn('[GTM] CRM enrichment failed:', e.message);
});
function enrichConversions(crmData) {
// Set as dataLayer properties so all subsequent events include them
window.dataLayer = window.dataLayer || [];
window.dataLayer.push(crmData); // Pushes without event — sets persistent values
}
})();
  1. Create a Custom HTML tag with the API fetch code above

    • Trigger: DOM Ready or Window Loaded depending on what the enrichment depends on
  2. Order your tags correctly

    If you want the enrichment data to be available when your GA4 tags fire, you need the enrichment tag to run first. Use Tag Sequencing on your main GA4 tags (Tags → [tag] → Tag Sequencing → Fire a tag before this tag) to ensure enrichment runs first.

    Or use a Custom Event trigger: push a geo_enriched event from the enrichment code, and only fire your GA4 Configuration tag when that event fires.

  3. Create Data Layer Variables for the enriched fields:

    • DLV - user_cityuser_city
    • DLV - account_tieraccount_tier
    • DLV - user_countryuser_country
  4. Add the enriched fields as User Properties on your Google Tag:

    In your Google Tag configuration → Configuration settings → User Properties:

    • user_city{{DLV - user_city}}
    • account_tier{{DLV - account_tier}}
  5. Test in Preview Mode

    The enrichment tag should fire first, followed by your Google Tag. Verify the user properties are populated in the Variables tab.

Every API call costs money (your provider’s API fees) and adds latency. Always cache results:

// Good caching patterns:
// Session cache (cleared when tab closes)
sessionStorage.setItem('enrichment_data', JSON.stringify(data));
// Short-lived cache (5 minutes)
var cacheEntry = { data: data, expires: Date.now() + 300000 };
localStorage.setItem('enrichment_cache', JSON.stringify(cacheEntry));
// Check before calling
var cached = localStorage.getItem('enrichment_cache');
if (cached) {
var entry = JSON.parse(cached);
if (entry.expires > Date.now()) return entry.data;
}

For high-traffic sites, move enrichment to server-side GTM where you can cache at the server level and avoid per-user API calls.

  1. Open GTM Preview and visit a page
  2. Watch the Summary pane — the enrichment tag should fire before your GA4 tags
  3. After the API response (1-2 seconds), the enriched event should appear
  4. Verify the enriched values are correct in the Variables tab
  5. Reload the page — the second load should use the session cache (faster, no API call)

Race condition: GA4 tags fire before enrichment completes. API calls are asynchronous. If your GA4 Configuration tag fires before the enrichment resolves, user properties will be empty. Use Tag Sequencing or the custom event approach to guarantee order.

CORS errors on external APIs. If the API does not allow requests from browser origins (no Access-Control-Allow-Origin header), the fetch will fail. You cannot fix CORS from GTM — you need to either use an API that supports CORS, or move the call to server-side GTM where CORS restrictions do not apply.

API keys exposed in the browser. Never put secret API keys in GTM Custom HTML tags — they are visible to anyone who views the page source. Use read-only API tokens with minimal permissions, or move sensitive API calls to server-side GTM.