Cross-Domain Tracking Overview
Cross-domain tracking enables you to track users as they move between different domains (e.g., from marketing site to checkout) while maintaining a single user session.
When You Need Cross-Domain Tracking
| Scenario | Example |
|---|---|
| Separate checkout domain | shop.example.com → checkout.example.com |
| Third-party payment | example.com → payments.stripe.com |
| Subdomain separation | www.example.com → blog.example.com |
| Multiple brand domains | brandA.com → brandB.com (same business) |
| Landing page domains | campaign.example.com → www.example.com |
How Cross-Domain Tracking Works
The Challenge
Without cross-domain tracking:
- User clicks ad on TikTok → lands on shop.example.com
- TikTok cookie stored on shop.example.com domain
- User navigates to checkout.example.com
- New domain = new session (cookie not accessible)
- Conversion doesn't attribute back to TikTok ad
The Solution
Cross-domain tracking passes the TikTok click ID (ttclid) in the URL:
- User clicks ad → lands on shop.example.com?ttclid=ABC123
- User navigates to checkout.example.com?ttclid=ABC123
- TikTok recognizes same user via ttclid
- Conversion properly attributes
Implementation Methods
Method 1: Automatic Linker (Subdomains)
For subdomains under the same root domain (e.g., shop.example.com and checkout.example.com):
// Load pixel with cookie domain configuration
ttq.load('YOUR_PIXEL_ID', {
cookie_domain: '.example.com' // Note the leading dot
});
ttq.page();
This shares cookies across all *.example.com subdomains.
Implementation:
<!-- On all subdomains -->
<script>
!function (w, d, t) {
// TikTok Pixel base code...
ttq.load('YOUR_PIXEL_ID', {
cookie_domain: '.example.com' // Shared cookie domain
});
ttq.page();
}(window, document, 'ttq');
</script>
Method 2: Manual Link Decoration (Different Domains)
For completely different domains, manually append ttclid to cross-domain links:
// Function to get ttclid from URL or cookie
function getTTClid() {
// Check URL first
var urlParams = new URLSearchParams(window.location.search);
var ttclid = urlParams.get('ttclid');
if (ttclid) return ttclid;
// Check cookie
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
if (cookie.indexOf('_ttp=') === 0) {
return cookie.substring('_ttp='.length);
}
}
return null;
}
// Function to append ttclid to URL
function appendTTClid(url) {
var ttclid = getTTClid();
if (!ttclid) return url;
var separator = url.indexOf('?') > -1 ? '&' : '?';
return url + separator + 'ttclid=' + encodeURIComponent(ttclid);
}
// Decorate all cross-domain links
function decorateLinks() {
var crossDomainHosts = [
'checkout.example.com',
'payment.example.com'
];
var links = document.querySelectorAll('a[href]');
links.forEach(function(link) {
var url = link.href;
// Check if link is cross-domain
crossDomainHosts.forEach(function(host) {
if (url.indexOf(host) > -1) {
link.href = appendTTClid(url);
}
});
});
}
// Run on page load
window.addEventListener('load', decorateLinks);
// Run on dynamically added links
var observer = new MutationObserver(decorateLinks);
observer.observe(document.body, {
childList: true,
subtree: true
});
Method 3: Form Decoration
For forms that submit to different domains:
// Decorate form action URLs
function decorateForms() {
var forms = document.querySelectorAll('form');
forms.forEach(function(form) {
var action = form.action;
// Check if form submits to cross-domain
if (action.indexOf('checkout.example.com') > -1) {
form.action = appendTTClid(action);
}
});
}
// Or add hidden input
function addTTClidToForm(formId) {
var form = document.getElementById(formId);
var ttclid = getTTClid();
if (ttclid && form) {
var input = document.createElement('input');
input.type = 'hidden';
input.name = 'ttclid';
input.value = ttclid;
form.appendChild(input);
}
}
// Usage
window.addEventListener('load', function() {
addTTClidToForm('checkout-form');
});
Complete Implementation Examples
Example 1: Marketing Site → Checkout Domain
Domain Setup:
- Marketing: www.example.com
- Checkout: checkout.example.com
- Payment: payments.example.com
On Marketing Site (www.example.com):
<script>
!function (w, d, t) {
// TikTok Pixel base code...
ttq.load('YOUR_PIXEL_ID');
ttq.page();
}(window, document, 'ttq');
// Cross-domain link decoration
(function() {
function getTTClid() {
var urlParams = new URLSearchParams(window.location.search);
var ttclid = urlParams.get('ttclid');
if (ttclid) return ttclid;
var match = document.cookie.match(/_ttp=([^;]+)/);
return match ? match[1] : null;
}
function decorateCrossDomainLinks() {
var ttclid = getTTClid();
if (!ttclid) return;
var links = document.querySelectorAll('a[href*="checkout.example.com"]');
links.forEach(function(link) {
var url = new URL(link.href);
url.searchParams.set('ttclid', ttclid);
link.href = url.toString();
});
}
window.addEventListener('load', decorateCrossDomainLinks);
// Handle dynamically added links
setInterval(decorateCrossDomainLinks, 1000);
})();
</script>
On Checkout Domain (checkout.example.com):
<script>
!function (w, d, t) {
// TikTok Pixel base code...
ttq.load('YOUR_PIXEL_ID');
ttq.page();
}(window, document, 'ttq');
// Preserve ttclid from URL
(function() {
var urlParams = new URLSearchParams(window.location.search);
var ttclid = urlParams.get('ttclid');
if (ttclid) {
// Store in cookie for this domain
document.cookie = '_ttp=' + ttclid + '; domain=checkout.example.com; path=/; max-age=' + (60*60*24*7);
}
// Decorate links to payment domain
function decoratePaymentLinks() {
var currentTTClid = ttclid || getCookie('_ttp');
if (!currentTTClid) return;
var links = document.querySelectorAll('a[href*="payments.example.com"]');
links.forEach(function(link) {
var url = new URL(link.href);
url.searchParams.set('ttclid', currentTTClid);
link.href = url.toString();
});
}
function getCookie(name) {
var match = document.cookie.match(new RegExp(name + '=([^;]+)'));
return match ? match[1] : null;
}
window.addEventListener('load', decoratePaymentLinks);
})();
</script>
Example 2: Subdomain Setup
For subdomains (blog.example.com, shop.example.com):
<!-- Same pixel code on all subdomains -->
<script>
!function (w, d, t) {
// TikTok Pixel base code...
ttq.load('YOUR_PIXEL_ID', {
cookie_domain: '.example.com' // Share cookies across subdomains
});
ttq.page();
}(window, document, 'ttq');
</script>
No link decoration needed - cookies automatically shared!
Example 3: Multiple Brand Domains
For completely different domains owned by same company:
Brand A (brandA.com):
<script>
!function (w, d, t) {
ttq.load('YOUR_PIXEL_ID');
ttq.page();
}(window, document, 'ttq');
// Decorate links to Brand B
window.addEventListener('load', function() {
var ttclid = new URLSearchParams(window.location.search).get('ttclid') ||
document.cookie.match(/_ttp=([^;]+)/)?.[1];
if (ttclid) {
document.querySelectorAll('a[href*="brandB.com"]').forEach(function(link) {
link.href += (link.href.includes('?') ? '&' : '?') + 'ttclid=' + ttclid;
});
}
});
</script>
Brand B (brandB.com):
<script>
!function (w, d, t) {
ttq.load('YOUR_PIXEL_ID'); // Same pixel ID
ttq.page();
}(window, document, 'ttq');
// Capture ttclid from URL
(function() {
var ttclid = new URLSearchParams(window.location.search).get('ttclid');
if (ttclid) {
document.cookie = '_ttp=' + ttclid + '; path=/; max-age=' + (60*60*24*7);
}
})();
</script>
JavaScript Helper Library
Create a reusable cross-domain tracking library:
// tiktok-cross-domain.js
(function(window) {
'use strict';
var TikTokCrossDomain = {
config: {
crossDomains: [],
paramName: 'ttclid',
cookieName: '_ttp',
cookieDuration: 7 * 24 * 60 * 60 * 1000 // 7 days
},
init: function(domains) {
this.config.crossDomains = domains || [];
this.captureTTClid();
this.decorateLinks();
this.observeDOM();
},
getTTClid: function() {
// From URL
var urlParams = new URLSearchParams(window.location.search);
var ttclid = urlParams.get(this.config.paramName);
if (ttclid) return ttclid;
// From cookie
var match = document.cookie.match(new RegExp(this.config.cookieName + '=([^;]+)'));
return match ? match[1] : null;
},
captureTTClid: function() {
var ttclid = new URLSearchParams(window.location.search).get(this.config.paramName);
if (ttclid) {
this.setCookie(this.config.cookieName, ttclid, this.config.cookieDuration);
}
},
setCookie: function(name, value, maxAge) {
document.cookie = name + '=' + value + '; path=/; max-age=' + (maxAge / 1000);
},
decorateUrl: function(url) {
var ttclid = this.getTTClid();
if (!ttclid) return url;
try {
var urlObj = new URL(url);
urlObj.searchParams.set(this.config.paramName, ttclid);
return urlObj.toString();
} catch (e) {
// Fallback for relative URLs
var separator = url.indexOf('?') > -1 ? '&' : '?';
return url + separator + this.config.paramName + '=' + encodeURIComponent(ttclid);
}
},
isCrossDomain: function(url) {
var self = this;
return this.config.crossDomains.some(function(domain) {
return url.indexOf(domain) > -1;
});
},
decorateLinks: function() {
var self = this;
var links = document.querySelectorAll('a[href]');
links.forEach(function(link) {
if (self.isCrossDomain(link.href)) {
link.href = self.decorateUrl(link.href);
}
});
},
decorateForms: function() {
var self = this;
var forms = document.querySelectorAll('form');
forms.forEach(function(form) {
if (self.isCrossDomain(form.action)) {
var ttclid = self.getTTClid();
if (ttclid) {
var input = document.createElement('input');
input.type = 'hidden';
input.name = self.config.paramName;
input.value = ttclid;
form.appendChild(input);
}
}
});
},
observeDOM: function() {
var self = this;
var observer = new MutationObserver(function() {
self.decorateLinks();
self.decorateForms();
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
};
// Expose globally
window.TikTokCrossDomain = TikTokCrossDomain;
})(window);
Usage:
<script src="tiktok-cross-domain.js"></script>
<script>
// Initialize with cross-domain list
TikTokCrossDomain.init([
'checkout.example.com',
'payments.example.com',
'app.example.com'
]);
</script>
Testing Cross-Domain Tracking
Manual Testing Steps
Add ttclid to URL manually
https://www.example.com/product?ttclid=TEST123456Navigate to cross-domain
- Click link to checkout.example.com
- Verify ttclid appears in URL
Check cookie on destination
// In browser console on checkout.example.com document.cookie // Should show: _ttp=TEST123456Fire conversion event
ttq.track('CompletePayment', { value: 1.00, currency: 'USD', order_id: 'TEST_' + Date.now() });Verify in Events Manager
- Check Test Events tool
- Should show same ttclid
Automated Testing
// test-cross-domain.js
describe('TikTok Cross-Domain Tracking', function() {
it('should preserve ttclid across domains', function() {
// Simulate landing with ttclid
window.history.pushState({}, '', '?ttclid=TEST123');
// Initialize cross-domain tracking
TikTokCrossDomain.init(['checkout.example.com']);
// Check ttclid extracted
var ttclid = TikTokCrossDomain.getTTClid();
expect(ttclid).toBe('TEST123');
// Check link decoration
var link = document.createElement('a');
link.href = 'https://checkout.example.com/cart';
document.body.appendChild(link);
TikTokCrossDomain.decorateLinks();
expect(link.href).toContain('ttclid=TEST123');
});
it('should store ttclid in cookie', function() {
window.history.pushState({}, '', '?ttclid=TEST456');
TikTokCrossDomain.captureTTClid();
var cookie = document.cookie;
expect(cookie).toContain('_ttp=TEST456');
});
});
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| ttclid not in URL | Links not decorated | Check decorateLinks function runs |
| Cookie not set on destination | ttclid not captured | Verify captureTTClid runs on page load |
| Conversion not attributing | Different pixel IDs | Use same pixel on all domains |
| ttclid value incorrect | Encoding issues | Use encodeURIComponent |
| SameSite cookie issues | Browser restrictions | Set SameSite=None; Secure |
Debug Mode
// Add debug logging
var TikTokCrossDomainDebug = {
log: function(message, data) {
if (localStorage.getItem('ttDebug') === 'true') {
console.log('[TikTok Cross-Domain]', message, data);
}
}
};
// Enable debugging
localStorage.setItem('ttDebug', 'true');
// Add to functions
TikTokCrossDomain.decorateUrl = function(url) {
var decorated = // ... decoration logic
TikTokCrossDomainDebug.log('URL decorated', {
original: url,
decorated: decorated
});
return decorated;
};
Best Practices
- Use same Pixel ID across all domains
- Test thoroughly before launch
- Document domains that need tracking
- Monitor attribution after implementation
- Use HTTPS on all domains
- Set proper cookie settings for cross-site scenarios
- Implement on page load not on click
- Handle dynamic content with MutationObserver
- Validate ttclid format before using
- Log implementations for debugging
Advanced: SameSite Cookie Handling
Modern browsers require SameSite attribute for cross-domain cookies:
function setTTClidCookie(ttclid) {
var cookieString = '_ttp=' + ttclid +
'; path=/' +
'; max-age=' + (60*60*24*7) +
'; SameSite=None' + // Required for cross-site
'; Secure'; // Required with SameSite=None (HTTPS only)
document.cookie = cookieString;
}
Next Steps
- Identify your domains that need cross-domain tracking
- Choose implementation method (subdomain vs different domains)
- Implement tracking code on all domains
- Test thoroughly using manual and automated tests
- Monitor Events Manager for proper attribution
- Document setup for team reference