Criteo supports both client-side (browser-based) and server-side (API-based) tracking implementations. This guide compares both approaches and provides implementation examples.
Overview
Client-Side Implementation
Traditional browser-based tracking using the Criteo OneTag JavaScript library.
How it works:
- User visits your website
- Browser loads Criteo OneTag script
- JavaScript fires events to Criteo servers
- Criteo sets cookies for user identification
- User is tracked across page views
Key Technology:
- JavaScript (OneTag)
- Browser cookies
- Client-side rendering
Server-Side Implementation
API-based tracking where your server communicates directly with Criteo's Event API.
How it works:
- User visits your website
- Your server detects events
- Server sends events to Criteo Event API
- Criteo processes events server-side
- Server maintains user session
Key Technology:
- Criteo Events API
- Server-side code (Python, Node.js, PHP, etc.)
- Server-side session management
Comparison
| Aspect | Client-Side | Server-Side |
|---|---|---|
| Implementation | Simple, drop-in JavaScript tag | Requires backend development |
| Performance | Minimal server load | Additional server processing |
| Privacy | Third-party cookies (blocked by some browsers) | First-party data, more privacy-friendly |
| Reliability | Affected by ad blockers | Not affected by ad blockers |
| iOS Tracking | Limited by ITP | Better iOS support |
| Data Control | Limited control over data sent | Full control over data |
| User Identification | Cookie-based | Server-managed sessions |
| Setup Time | Minutes | Hours to days |
| Maintenance | Low | Medium |
| Cross-Domain | Automatic with OneTag | Requires custom implementation |
When to Use Each Approach
Use Client-Side When:
- Quick implementation is priority
- Limited development resources
- Standard e-commerce tracking is sufficient
- Desktop traffic is dominant
- Ad blocker impact is acceptable
- Third-party cookies are not a concern
Use Server-Side When:
- Enhanced privacy compliance is required
- iOS/Safari traffic is significant
- Ad blocker bypass is important
- Full data control is needed
- Custom event tracking is required
- You have development resources
- Existing server-side infrastructure available
Hybrid Approach
Many businesses use both:
- Client-side for immediate user tracking
- Server-side for conversion events and critical data
Client-Side Implementation
Basic Setup
<!-- Standard OneTag Implementation -->
<script type="text/javascript" src="//dynamic.criteo.com/js/ld/ld.js" async="true"></script>
<script type="text/javascript">
window.criteo_q = window.criteo_q || [];
var deviceType = /iPad/.test(navigator.userAgent) ? "t" :
/Mobile|iP(hone|od)|Android|BlackBerry|IEMobile|Silk/.test(navigator.userAgent) ? "m" : "d";
window.criteo_q.push(
{ event: "setAccount", account: 12345 },
{ event: "setSiteType", type: deviceType },
{ event: "viewHome" }
);
</script>
Advantages
1. Easy Implementation
// No backend changes required
// Just add JavaScript to pages
2. Automatic Features
// Automatic cookie syncing
// Cross-device tracking via Criteo's device graph
// Automatic user matching
3. Real-Time Tracking
// Immediate event firing
// No server-side latency
// Direct browser-to-Criteo communication
Limitations
1. Ad Blocker Impact
// OneTag blocked by ad blockers
if (typeof window.criteo_q === 'undefined') {
console.log('Criteo blocked by ad blocker');
// ~25-40% of users may have ad blockers
}
2. Privacy Restrictions
// Safari ITP limits cookie duration
// Firefox blocks third-party cookies
// Chrome preparing Privacy Sandbox changes
3. Limited iOS Tracking
// iOS 14+ App Tracking Transparency
// Safari ITP 2.3+ restrictions
// Reduced attribution window
Server-Side Implementation
Criteo Events API
Criteo provides an Events API for server-side tracking.
API Authentication
// Node.js example
const axios = require('axios');
const CRITEO_API_ENDPOINT = 'https://api.criteo.com/2023-04/events';
const CLIENT_ID = 'your_client_id';
const CLIENT_SECRET = 'your_client_secret';
// Get access token
async function getCriteoAccessToken() {
const response = await axios.post('https://api.criteo.com/oauth2/token', {
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
grant_type: 'client_credentials'
});
return response.data.access_token;
}
Send Events
// Track product view event
async function trackProductView(userId, productId) {
const accessToken = await getCriteoAccessToken();
const event = {
account_id: '12345',
events: [{
event_type: 'viewItem',
timestamp: new Date().toISOString(),
user: {
user_id: userId,
email: hashEmail(userEmail) // SHA-256 hashed
},
product: {
id: productId
},
device: {
type: getDeviceType(req.headers['user-agent'])
}
}]
};
const response = await axios.post(CRITEO_API_ENDPOINT, event, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
return response.data;
}
Complete Server-Side Example
Node.js/Express:
const express = require('express');
const axios = require('axios');
const crypto = require('crypto');
const app = express();
const CRITEO_ACCOUNT_ID = '12345';
// Hash email for privacy
function hashEmail(email) {
return crypto
.createHash('sha256')
.update(email.toLowerCase().trim())
.digest('hex');
}
// Detect device type
function getDeviceType(userAgent) {
if (/iPad/i.test(userAgent)) return 't';
if (/Mobile|Android|iP(hone|od)/i.test(userAgent)) return 'm';
return 'd';
}
// Product page route
app.get('/product/:id', async (req, res) => {
const productId = req.params.id;
const userId = req.session.userId;
const userEmail = req.session.userEmail;
// Send event to Criteo
try {
await sendCriteoEvent({
event_type: 'viewItem',
user_id: userId,
email: userEmail ? hashEmail(userEmail) : null,
product_id: productId,
device_type: getDeviceType(req.headers['user-agent'])
});
} catch (error) {
console.error('Criteo tracking error:', error);
// Continue rendering page even if tracking fails
}
// Render product page
res.render('product', { productId });
});
// Transaction route
app.post('/order/complete', async (req, res) => {
const { orderId, items, userId, userEmail } = req.body;
// Send transaction to Criteo
try {
await sendCriteoEvent({
event_type: 'trackTransaction',
user_id: userId,
email: hashEmail(userEmail),
transaction_id: orderId,
items: items.map(item => ({
id: item.productId,
price: item.price,
quantity: item.quantity
})),
device_type: getDeviceType(req.headers['user-agent'])
});
} catch (error) {
console.error('Criteo transaction tracking error:', error);
}
res.json({ success: true });
});
// Helper function to send events to Criteo
async function sendCriteoEvent(eventData) {
const accessToken = await getCriteoAccessToken();
const payload = {
account_id: CRITEO_ACCOUNT_ID,
events: [{
event_type: eventData.event_type,
timestamp: new Date().toISOString(),
user: {
user_id: eventData.user_id,
email: eventData.email
},
device: {
type: eventData.device_type
}
}]
};
// Add event-specific data
if (eventData.product_id) {
payload.events[0].product = { id: eventData.product_id };
}
if (eventData.transaction_id) {
payload.events[0].transaction = {
id: eventData.transaction_id,
items: eventData.items
};
}
const response = await axios.post(
'https://api.criteo.com/2023-04/events',
payload,
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
}
);
return response.data;
}
app.listen(3000);
Python/Flask:
from flask import Flask, request, session
import requests
import hashlib
from datetime import datetime
app = Flask(__name__)
CRITEO_ACCOUNT_ID = '12345'
CRITEO_API_URL = 'https://api.criteo.com/2023-04/events'
def hash_email(email):
"""Hash email using SHA-256"""
return hashlib.sha256(email.lower().strip().encode()).hexdigest()
def get_device_type(user_agent):
"""Determine device type from user agent"""
if 'iPad' in user_agent:
return 't'
if any(x in user_agent for x in ['Mobile', 'Android', 'iPhone', 'iPod']):
return 'm'
return 'd'
def get_access_token():
"""Get Criteo API access token"""
response = requests.post(
'https://api.criteo.com/oauth2/token',
data={
'client_id': 'your_client_id',
'client_secret': 'your_client_secret',
'grant_type': 'client_credentials'
}
)
return response.json()['access_token']
def send_criteo_event(event_data):
"""Send event to Criteo Events API"""
access_token = get_access_token()
payload = {
'account_id': CRITEO_ACCOUNT_ID,
'events': [{
'event_type': event_data['event_type'],
'timestamp': datetime.utcnow().isoformat() + 'Z',
'user': {
'user_id': event_data.get('user_id'),
'email': event_data.get('email')
},
'device': {
'type': event_data['device_type']
}
}]
}
# Add event-specific data
if 'product_id' in event_data:
payload['events'][0]['product'] = {'id': event_data['product_id']}
if 'transaction' in event_data:
payload['events'][0]['transaction'] = event_data['transaction']
response = requests.post(
CRITEO_API_URL,
json=payload,
headers={
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
}
)
return response.json()
@app.route('/product/<product_id>')
def product_page(product_id):
"""Product page with server-side tracking"""
try:
send_criteo_event({
'event_type': 'viewItem',
'user_id': session.get('user_id'),
'email': hash_email(session.get('user_email', '')),
'product_id': product_id,
'device_type': get_device_type(request.user_agent.string)
})
except Exception as e:
print(f'Criteo tracking error: {e}')
return render_template('product.html', product_id=product_id)
@app.route('/order/complete', methods=['POST'])
def complete_order():
"""Order completion with transaction tracking"""
data = request.json
try:
send_criteo_event({
'event_type': 'trackTransaction',
'user_id': data['user_id'],
'email': hash_email(data['user_email']),
'device_type': get_device_type(request.user_agent.string),
'transaction': {
'id': data['order_id'],
'items': [
{
'id': item['product_id'],
'price': item['price'],
'quantity': item['quantity']
}
for item in data['items']
]
}
})
except Exception as e:
print(f'Criteo transaction tracking error: {e}')
return {'success': True}
if __name__ == '__main__':
app.run()
PHP:
<?php
// Criteo server-side tracking
class CriteoTracker {
private $accountId = '12345';
private $apiUrl = 'https://api.criteo.com/2023-04/events';
private $clientId = 'your_client_id';
private $clientSecret = 'your_client_secret';
public function getAccessToken() {
$ch = curl_init('https://api.criteo.com/oauth2/token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'grant_type' => 'client_credentials'
]));
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
return $response['access_token'];
}
public function hashEmail($email) {
return hash('sha256', strtolower(trim($email)));
}
public function getDeviceType($userAgent) {
if (preg_match('/iPad/', $userAgent)) return 't';
if (preg_match('/Mobile|Android|iPhone|iPod/', $userAgent)) return 'm';
return 'd';
}
public function trackEvent($eventData) {
$accessToken = $this->getAccessToken();
$payload = [
'account_id' => $this->accountId,
'events' => [[
'event_type' => $eventData['event_type'],
'timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
'user' => [
'user_id' => $eventData['user_id'] ?? null,
'email' => $eventData['email'] ?? null
],
'device' => [
'type' => $eventData['device_type']
]
]]
];
// Add product data
if (isset($eventData['product_id'])) {
$payload['events'][0]['product'] = ['id' => $eventData['product_id']];
}
// Add transaction data
if (isset($eventData['transaction'])) {
$payload['events'][0]['transaction'] = $eventData['transaction'];
}
$ch = curl_init($this->apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $accessToken,
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
}
// Usage
$tracker = new CriteoTracker();
// Track product view
$tracker->trackEvent([
'event_type' => 'viewItem',
'user_id' => $_SESSION['user_id'],
'email' => $tracker->hashEmail($_SESSION['user_email']),
'product_id' => $_GET['product_id'],
'device_type' => $tracker->getDeviceType($_SERVER['HTTP_USER_AGENT'])
]);
?>
Hybrid Implementation
Combine client-side and server-side for best results:
Client-Side for Browsing
<!-- Use OneTag for immediate page tracking -->
<script type="text/javascript" src="//dynamic.criteo.com/js/ld/ld.js" async="true"></script>
<script type="text/javascript">
window.criteo_q = window.criteo_q || [];
window.criteo_q.push(
{ event: "setAccount", account: 12345 },
{ event: "setSiteType", type: deviceType },
{ event: "viewItem", item: "PROD_123" }
);
</script>
Server-Side for Conversions
// Use server-side API for critical conversion events
app.post('/checkout/complete', async (req, res) => {
// Track via server-side API for reliability
await sendCriteoEvent({
event_type: 'trackTransaction',
transaction_id: req.body.orderId,
items: req.body.items
});
// Also send client-side as backup
res.render('confirmation', {
criteoTransactionData: req.body
});
});
Migration Strategy
From Client-Side to Hybrid
Phase 1: Add Server-Side Tracking
- Implement server-side for transactions only
- Keep existing client-side implementation
- Run in parallel for 2-4 weeks
Phase 2: Validate Data
- Compare client-side vs server-side conversion data
- Identify discrepancies
- Adjust implementation
Phase 3: Expand Server-Side
- Add server-side tracking for other events
- Gradually reduce client-side dependency
Phase 4: Optimize
- Fine-tune server-side implementation
- Remove redundant client-side events
- Monitor performance
Performance Considerations
Client-Side Performance
// Optimize client-side loading
// Load asynchronously
<script async src="//dynamic.criteo.com/js/ld/ld.js"></script>
// Use resource hints
<link rel="preconnect" href="https://dynamic.criteo.com">
<link rel="dns-prefetch" href="https://gum.criteo.com">
Server-Side Performance
// Use async processing
async function trackEventAsync(eventData) {
// Don't block response
setImmediate(() => {
sendCriteoEvent(eventData).catch(err => {
console.error('Criteo tracking failed:', err);
});
});
}
// Batch events
const eventQueue = [];
setInterval(() => {
if (eventQueue.length > 0) {
sendCriteoEventBatch(eventQueue.splice(0));
}
}, 5000); // Send every 5 seconds
Best Practices
Client-Side
- Load asynchronously to avoid blocking page render
- Use data layer for consistent data structure
- Implement consent management for privacy compliance
- Test with ad blockers to understand impact
Server-Side
- Handle errors gracefully - don't block user experience
- Use async processing - don't delay responses
- Implement retry logic for failed API calls
- Monitor API performance and quotas
- Cache access tokens to reduce API calls
Both Approaches
- Consistent user identification across methods
- Deduplicate events to avoid double-counting
- Test thoroughly before production deployment
- Monitor data quality in Criteo dashboard
Next Steps
- Event Tracking - Implement specific events
- Cross-Domain Tracking - Track across domains
- Troubleshooting - Debug implementation issues