Tealium + ChatGPT Apps
This guide shows how to implement Tealium tracking in a ChatGPT app with a single, consistent visitor identity across client-side, server-side, and hybrid approaches.
Introduction
Tealium offers a data foundation for app-like experiences in browsers, widgets, and custom ChatGPT interfaces. It supports consistent data collection and visitor identity management across platforms.
This solution for ChatGPT uses an MCP-friendly design to achieve the following:
- Track key interactions (product views, button clicks, purchases).
- Unify identity across client, server, and ChatGPT contexts.
- Stream events in real time using Tealium Collect.
- Personalize experiences using Tealium Moments API and Tealium AudienceStream.
Option 1: Client-side tracking
This approach uses the standard Universal Tag (utag.js) installation from a dedicated profile in Tealium iQ. The only difference is that it does not rely on the anonymous ID generated by utag.js, so it generates and persists its own ID using utility functions.
Benefits:
- Full access to the Tealium iQ Tag Management tag vendor marketplace.
- Built-in support for consent.
- Supports A/B testing and personalization triggers.
The following sample code demonstrates the following:
- Load
utag.js, just like you do on a web site. - Generate and persist an anonymous visitor ID.
- Track the initial loading of the ChatGPT app.
- Track related user actions with custom methods.
<script>
// Load Tealium iQ
(function(a,b,c,d){
a='https://tags.tiqcdn.com/utag/[ACCOUNT]/[PROFILE]/[ENVIRONMENT]/utag.js';
b=document;c='script';d=b.createElement(c);d.src=a;
d.type='text/java'+c;d.async=true;
a=b.getElementsByTagName(c)[0];a.parentNode.insertBefore(d,a);
})();
// Generate a 32-char lowercase alphanumeric visitor ID
function generateTealiumVisitorId() {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
return Array.from({ length: 32 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
}
// Retrieve or create & persist the visitor ID
let tealium_visitor_id = localStorage.getItem('tealium_visitor_id');
if (!tealium_visitor_id) {
tealium_visitor_id = generateTealiumVisitorId();
localStorage.setItem('tealium_visitor_id', tealium_visitor_id);
}
// App initialized
utag.track('event', {
'tealium_event': 'interface_loaded',
'app_version': '2.1.0',
'user_tier': 'enterprise',
'tealium_visitor_id': tealium_visitor_id
});
// PDP view
function trackViewPdp(product) {
utag.track('view', {
'tealium_event': 'view_pdp',
'product_id': product.id,
'product_name': product.name,
'category': product.category,
'price': product.price,
'currency': product.currency || 'USD',
'tealium_visitor_id': tealium_visitor_id
});
}
// Button click
function trackButtonClick(button) {
utag.track('link', {
'tealium_event': 'button_click',
'button_id': button.id,
'button_text': button.text,
'location': button.location || 'pdp',
'app_version': '2.1.0',
'tealium_visitor_id': tealium_visitor_id
});
}
</script>
Option 2: Server-side tracking
Server-side tracking sends events directly to Tealium using the HTTP API. Similar to the client-side solution, it generates and persists its own anonymous visitor ID using added utility functions.
We recommend this approach for the following scenarios:
- When CSP restrictions prevent external JavaScript from loading.
- To guarantee data tracking for essential events such as purchases.
The following server-side tracking solutions are available:
Node.js apps (Recommended)
The Node.js solution has client code that generates and persists the anonymous visitor ID and calls wrapper functions for tracked events.
For example, the following sample code generates the visitor ID and sends order tracking requests to the server. The order object comes from your app when a customer completes a purchase.
// Generate a 32-char lowercase alphanumeric visitor ID
function generateTealiumVisitorId() {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
return Array.from({ length: 32 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
}
// Retrieve or create & persist the visitor ID
let tealium_visitor_id = localStorage.getItem('tealium_visitor_id');
if (!tealium_visitor_id) {
tealium_visitor_id = generateTealiumVisitorId();
localStorage.setItem('tealium_visitor_id', tealium_visitor_id);
}
async function trackPurchaseServer(order) {
await fetch('/api/tealium/track', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
'tealium_event': 'purchase',
'tealium_visitor_id': tealium_visitor_id,
'order_id': order.id,
'order_total': order.total,
'currency': order.currency || 'USD',
'items': order.items
})
});
}
The following sample server-side code sets up an endpoint to accept tracking calls and uses Tealium for Node.js to forward those events on to Tealium Collect.
// npm i tealium-collect express
const express = require('express');
const TealiumCollect = require('tealium-collect');
const app = express();
app.use(express.json());
const tealium = new TealiumCollect({
'account': 'your-account',
'profile': 'chatgpt-app',
'environment': 'prod'
});
app.post('/api/tealium/track', async (req, res) => {
try {
const tealium_visitor_id = req.body.tealium_visitor_id;
if (!/^[a-z0-9]{32}$/.test(tealium_visitor_id || '')) {
return res.status(400).json({ error: 'Invalid or missing tealium_visitor_id' });
}
const allowed = new Set(['interface_loaded','view_pdp','button_click','purchase']);
if (!allowed.has(req.body.tealium_event)) {
return res.status(400).json({ error: 'Unsupported event type' });
}
await tealium.track({
...req.body,
tealium_visitor_id,
'timestamp': req.body.timestamp || new Date().toISOString()
});
res.status(204).end();
} catch (e) {
console.error('Tealium track error:', e);
res.status(500).json({ error: 'Tracking failed' });
}
});
app.listen(3000, () => console.log('Tealium server listening on 3000'));
Browser apps
Use this approach for browser apps where you have access to embed <script> tags. This code uses fetch() and the HTTP API to send events directly to Tealium and uses the same utility methods to generate and persist an anonymous visitor ID.
<script>
// Generate a 32-char lowercase alphanumeric visitor ID
function generateTealiumVisitorId() {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
return Array.from({ length: 32 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
}
// Retrieve or create & persist the visitor ID
let tealium_visitor_id = localStorage.getItem('tealium_visitor_id');
if (!tealium_visitor_id) {
tealium_visitor_id = generateTealiumVisitorId();
localStorage.setItem('tealium_visitor_id', tealium_visitor_id);
}
async function sendCollectEvent(payload) {
const base = {
'tealium_account': 'your-account',
'tealium_profile': 'chatgpt-app',
'tealium_visitor_id': tealium_visitor_id,
'timestamp': new Date().toISOString()
};
await fetch('https://collect.tealiumiq.com/event', {
'method': 'POST',
'headers': { 'Content-Type': 'application/json' },
'body': JSON.stringify({ ...base, ...payload })
});
}
function trackViewPdpServer(product) {
return sendCollectEvent({
'tealium_event': 'view_pdp',
'product_id': product.id,
'product_name': product.name,
'product_category': product.category,
'product_price': product.price,
'product_currency': product.currency || 'USD'
});
}
function trackButtonClickServer(button) {
return sendCollectEvent({
'tealium_event': 'button_click',
'button_id': button.id,
'button_text': button.text,
'location': button.location || 'pdp',
'app_version': '2.1.0'
});
}
</script>
Option 3: Hybrid (Recommended)
A hybrid approach uses the client-side solution to track interaction events, such as interface_loaded, view_pdp, and button_click, and the server-side solution to track authoritative events such as purchase. We recommend this approach to achieve the most complete tracking of your app.
When running the hybrid solution, do not load the Tealium Collect tag in your iQ profile. This prevents duplicate event tracking.
Visitor identity
To ensure accurate tracking and a unified visitor profile, always use the same anonymous visitor ID for both client-side and server-side events. This approach maintains a single visitor identity across ChatGPT, web, and mobile, enabling consistent tracking and real-time personalization.
We recommend including known user identifiers as separate parameters when available, for example customer_id or email_address_hash, but do not use these values to replace tealium_visitor_id.
MCP integration (Optional)
Expose a simple MCP tool from your server for ChatGPT to call. This approach lets ChatGPT handle conversational logic, while your server enforces format, identity, and policy.
Tool: tealium-track-event
Example schema:
{
"account": "your-account",
"profile": "chatgpt-app",
"environment": "prod",
"event": "view_pdp",
"visitorId": "abcdefghijklmnopqrstuvwxyz012345",
"data": {
"product_id": "widget-123",
"price": 29.99,
"currency": "USD"
}
}
Validation:
visitorIdmust match the regular expression/^[a-z0-9]{32}$/.eventmust be in list of allowed events (interface_loaded,view_pdp,button_click,purchase).
Moments API MCP
Add the Moments MCP server to your app to enable personalization.
Example flow:
- Track events
view_pdp,button_click, andpurchase. - AudienceStream builds the visitor profile.
- Chat UI (or MCP tool) queries Moments API with
tealium_visitor_id. - Responses adapt (recommendations, offers, tone).
Data privacy and compliance
This app supports data privacy and compliance through the following features:
- Consent-driven activation via Tealium iQ Consent Manager.
- PII governance through omitting or hashing before transmitting data.
- Regional compliance (GDPR, CCPA, and data residency).
Sample app
Prerequisites
- Node.js 18+ (LTS)
npmorpnpm- Tealium account, profile, and environment
- (Optional) ngrok for MCP exposure
Install
- Install dependencies for the main project:
pnpm install - Install dependencies for the server:
cd server pnpm install cd ..
Run the application
- Build the frontend bundle:
pnpm run build - Start the MCP server:
cd server pnpm start - The server runs at http://localhost:8000/mcp
- Generate an ngrok tunnel (in a new terminal):
ngrok http 8000 - ngrok returns the following output:
Session Status online Account Your Account (Plan: Free) Update update available (version 3.x.x, Ctrl-C to update) Version 3.x.x Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding https://abc123def456.ngrok-free.app -> http://localhost:8000 - Update the CSP domains in server code:
- Copy the ngrok URL from the terminal output (for example:
https://abc123def456.ngrok-free.app). - In
server/index.tson lines 30 and 34, replacehttps://resolvedly-pouched-nena.ngrok-free.devwith your new ngrok URL. - Make the same update in
src/tealium/TealiumTracker.tsxon line 134.
- Copy the ngrok URL from the terminal output (for example:
- Restart the server:
# Kill the current server (Ctrl+C) cd server pnpm start - Rebuild the frontend with the new URL:
cd .. pnpm run build
Make note of the following access points:
- Local MCP Server: http://localhost:8000/mcp
- Health Check: http://localhost:8000/health
- API Endpoint: http://localhost:8000/api/tealium/track
- Public ngrok URL: ngrok URL +
/mcp - Public API: ngrok URL +
/api/tealium/track
Add the app in ChatGPT
- Go to ChatGPT > Settings > Apps and Connectors.
- Enable Developer Mode under Advanced Settings.
- Paste your ngrok URL in the MCP server field.
Launch the Tealium app
- When prompted, use the
tealium-trackertool. - Enter your Tealium account, profile, and environment.
- The Tealium Universal tag (
utag.js) loads and tracks events as if in a web app.
If third-party libraries are blocked, allow their resources in your CSP.
Server-side event tracking
Turning on server-side tracking sends client-side calls to /api/tealium/track, which uses the Tealium Collect NPM module to make tracking requests.
Validate results in Tealium Trace.
This page was last updated: October 29, 2025