Criteo Server-Side vs Client-Side Implementation | Blue Frog Docs

Criteo Server-Side vs Client-Side Implementation

Compare server-side and client-side Criteo implementations, including benefits, trade-offs, and implementation guides for each approach.

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:

  1. User visits your website
  2. Browser loads Criteo OneTag script
  3. JavaScript fires events to Criteo servers
  4. Criteo sets cookies for user identification
  5. User is tracked across page views

Key Technology:

Server-Side Implementation

API-based tracking where your server communicates directly with Criteo's Event API.

How it works:

  1. User visits your website
  2. Your server detects events
  3. Server sends events to Criteo Event API
  4. Criteo processes events server-side
  5. 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

  1. Load asynchronously to avoid blocking page render
  2. Use data layer for consistent data structure
  3. Implement consent management for privacy compliance
  4. Test with ad blockers to understand impact

Server-Side

  1. Handle errors gracefully - don't block user experience
  2. Use async processing - don't delay responses
  3. Implement retry logic for failed API calls
  4. Monitor API performance and quotas
  5. Cache access tokens to reduce API calls

Both Approaches

  1. Consistent user identification across methods
  2. Deduplicate events to avoid double-counting
  3. Test thoroughly before production deployment
  4. Monitor data quality in Criteo dashboard

Next Steps

// SYS.FOOTER