AdRoll Server-Side vs Client-Side Tracking | Blue Frog Docs

AdRoll Server-Side vs Client-Side Tracking

Compare client-side JavaScript pixel tracking with server-side API implementation for AdRoll, including hybrid approaches and best practices.

Tracking Methods Overview

AdRoll supports two primary tracking approaches: client-side (JavaScript pixel in browser) and server-side (API calls from your backend). Each has trade-offs in implementation complexity, data accuracy, privacy compliance, and ad blocker resilience. Most implementations use a hybrid approach combining both methods for optimal coverage and accuracy.

Quick Comparison

Aspect Client-Side (Pixel) Server-Side (API)
Implementation Simple (paste snippet) Complex (backend integration)
Ad blocker impact High (30-40% blocked) None (bypasses blockers)
Data accuracy Lower (blockers, ITP) Higher (all events captured)
User context Full (IP, user agent, referrer) Limited (must pass manually)
Cookie access Automatic Manual (must sync)
Real-time data Yes Yes
Privacy controls Browser-dependent Full control
Setup time 5-30 minutes 1-4 hours

Client-Side Tracking (JavaScript Pixel)

How It Works

JavaScript pixel loads in user's browser and:

  1. Sets first-party cookies to identify user
  2. Captures pageviews, clicks, and conversions
  3. Sends data to AdRoll servers via HTTP requests
  4. Automatically collects browser context (IP, user agent, referrer)

Implementation

<!-- Paste in <head> of all pages -->
<script type="text/javascript">
  adroll_adv_id = "ABC123XYZ";
  adroll_pix_id = "DEF456GHI";
  adroll_version = "2.0";

  (function(w, d, e, o, a) {
    w.__adroll_loaded = true;
    w.adroll = w.adroll || [];
    w.adroll.f = [ 'setProperties', 'identify', 'track' ];
    var roundtripUrl = "https://s.adroll.com/j/" + adroll_adv_id + "/roundtrip.js";
    for (a = 0; a < w.adroll.f.length; a++) {
      w.adroll[w.adroll.f[a]] = w.adroll[w.adroll.f[a]] || (function(n) {
        return function() {
          w.adroll.push([ n, arguments ])
        }
      })(w.adroll.f[a])
    }
    e = d.createElement('script');
    o = d.getElementsByTagName('script')[0];
    e.async = 1;
    e.src = roundtripUrl;
    o.parentNode.insertBefore(e, o);
  })(window, document);
</script>

Track events:

// Purchase conversion
adroll.track("purchase", {
  order_id: "ORD-123",
  conversion_value: 99.99,
  currency: "USD"
});

// Product pageview
adroll.track("pageView", {
  product_id: "SKU-001",
  price: 49.99
});

Advantages

1. Simple implementation:

  • Copy-paste code snippet
  • No backend changes required
  • Works with any website platform
  • Automatically collects user context

2. Automatic data collection:

// Pixel automatically captures:
- IP address → geolocation
- User agent → device/browser
- Referrer → traffic source
- Cookies → user identification
- Page URL → content context

3. Real-time audience building:

  • Users enter audiences immediately
  • No data processing delay
  • Dynamic remarketing works instantly

4. E-commerce integrations:

  • Shopify, WooCommerce apps use client-side tracking
  • Product catalog syncs automatically
  • Zero backend code required

Disadvantages

1. Ad blocker impact (30-40% data loss):

// Ad blockers (uBlock Origin, AdBlock Plus) block:
- Script loading from s.adroll.com
- HTTP requests to d.adroll.com
- Cookie setting

Result: 30-40% of users not tracked

2. Browser privacy features:

  • Safari ITP: Limits cookie lifespan to 7 days
  • Firefox ETP: Blocks known tracking domains
  • Chrome Privacy Sandbox: Future restrictions coming

3. Client-side performance impact:

// Pixel adds to page load:
- Initial script: ~15KB (gzipped)
- Additional requests: 2-3 per pageview
- Minimal but measurable performance cost

4. Depends on user's browser:

// Fails if:
- JavaScript disabled (~0.2% of users)
- Network errors during script load
- Slow connections timeout before pixel loads

Server-Side Tracking (API)

How It Works

Your backend server sends events directly to AdRoll API:

  1. User completes action (purchase, sign-up, etc.)
  2. Your server sends event to AdRoll API
  3. AdRoll processes event and attributes to user
  4. No browser-side JavaScript required

Implementation

API Endpoint:

POST https://services.adroll.com/api/v1/track
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

Request body:

{
  "advertiser_id": "ABC123XYZ",
  "pixel_id": "DEF456GHI",
  "event": "purchase",
  "user_id": "hashed_email_or_cookie_id",
  "event_id": "unique_event_id",
  "properties": {
    "order_id": "ORD-123",
    "conversion_value": 99.99,
    "currency": "USD",
    "products": [
      {
        "product_id": "SKU-001",
        "quantity": 1,
        "price": 99.99
      }
    ]
  },
  "timestamp": 1640000000,
  "user_data": {
    "ip_address": "192.168.1.1",
    "user_agent": "Mozilla/5.0..."
  }
}

Example Implementations

Node.js (Express):

const axios = require('axios');
const crypto = require('crypto');

// Helper: Hash email for privacy
function hashEmail(email) {
  return crypto.createHash('sha256')
    .update(email.toLowerCase().trim())
    .digest('hex');
}

// Track conversion server-side
async function trackAdRollConversion(orderData, userContext) {
  try {
    const response = await axios.post(
      'https://services.adroll.com/api/v1/track',
      {
        advertiser_id: process.env.ADROLL_ADV_ID,
        pixel_id: process.env.ADROLL_PIX_ID,
        event: 'purchase',
        user_id: hashEmail(orderData.customerEmail),
        event_id: `order_${orderData.orderId}`,
        properties: {
          order_id: orderData.orderId,
          conversion_value: orderData.total,
          currency: orderData.currency,
          products: orderData.items.map(item => ({
            product_id: item.sku,
            quantity: item.quantity,
            price: item.price
          }))
        },
        timestamp: Math.floor(Date.now() / 1000),
        user_data: {
          ip_address: userContext.ipAddress,
          user_agent: userContext.userAgent
        }
      },
      {
        headers: {
          'Authorization': `Bearer ${process.env.ADROLL_API_KEY}`,
          'Content-Type': 'application/json'
        }
      }
    );

    console.log('AdRoll conversion tracked:', response.data);
    return { success: true };
  } catch (error) {
    console.error('AdRoll API error:', error.response?.data || error.message);
    return { success: false, error: error.message };
  }
}

// Express route: Checkout completion
app.post('/api/checkout', async (req, res) => {
  // 1. Process payment
  const order = await processPayment(req.body);

  // 2. Track conversion server-side
  await trackAdRollConversion(
    {
      orderId: order.id,
      customerEmail: order.email,
      total: order.total,
      currency: order.currency,
      items: order.lineItems
    },
    {
      ipAddress: req.ip,
      userAgent: req.headers['user-agent']
    }
  );

  // 3. Send response
  res.json({ success: true, orderId: order.id });
});

Python (Flask):

import requests
import hashlib
import time
import os

def hash_email(email):
    """Hash email for privacy"""
    return hashlib.sha256(email.lower().strip().encode()).hexdigest()

def track_adroll_conversion(order_data, user_context):
    """Send conversion to AdRoll API"""
    url = "https://services.adroll.com/api/v1/track"

    headers = {
        "Authorization": f"Bearer {os.getenv('ADROLL_API_KEY')}",
        "Content-Type": "application/json"
    }

    payload = {
        "advertiser_id": os.getenv('ADROLL_ADV_ID'),
        "pixel_id": os.getenv('ADROLL_PIX_ID'),
        "event": "purchase",
        "user_id": hash_email(order_data['customer_email']),
        "event_id": f"order_{order_data['order_id']}",
        "properties": {
            "order_id": order_data['order_id'],
            "conversion_value": order_data['total'],
            "currency": order_data['currency'],
            "products": [
                {
                    "product_id": item['sku'],
                    "quantity": item['quantity'],
                    "price": item['price']
                }
                for item in order_data['items']
            ]
        },
        "timestamp": int(time.time()),
        "user_data": {
            "ip_address": user_context['ip_address'],
            "user_agent": user_context['user_agent']
        }
    }

    try:
        response = requests.post(url, json=payload, headers=headers)
        response.raise_for_status()
        print(f"AdRoll conversion tracked: {response.json()}")
        return {"success": True}
    except requests.exceptions.RequestException as e:
        print(f"AdRoll API error: {e}")
        return {"success": False, "error": str(e)}

# Flask route
@app.route('/api/checkout', methods=['POST'])
def checkout():
    # Process payment
    order = process_payment(request.json)

    # Track server-side
    track_adroll_conversion(
        {
            "order_id": order['id'],
            "customer_email": order['email'],
            "total": order['total'],
            "currency": order['currency'],
            "items": order['line_items']
        },
        {
            "ip_address": request.remote_addr,
            "user_agent": request.headers.get('User-Agent')
        }
    )

    return jsonify({"success": True, "order_id": order['id']})

PHP:

<?php
function hashEmail($email) {
  return hash('sha256', strtolower(trim($email)));
}

function trackAdRollConversion($orderData, $userContext) {
  $url = 'https://services.adroll.com/api/v1/track';

  $payload = [
    'advertiser_id' => getenv('ADROLL_ADV_ID'),
    'pixel_id' => getenv('ADROLL_PIX_ID'),
    'event' => 'purchase',
    'user_id' => hashEmail($orderData['customer_email']),
    'event_id' => 'order_' . $orderData['order_id'],
    'properties' => [
      'order_id' => $orderData['order_id'],
      'conversion_value' => $orderData['total'],
      'currency' => $orderData['currency'],
      'products' => array_map(function($item) {
        return [
          'product_id' => $item['sku'],
          'quantity' => $item['quantity'],
          'price' => $item['price']
        ];
      }, $orderData['items'])
    ],
    'timestamp' => time(),
    'user_data' => [
      'ip_address' => $userContext['ip_address'],
      'user_agent' => $userContext['user_agent']
    ]
  ];

  $ch = curl_init($url);
  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 ' . getenv('ADROLL_API_KEY'),
    'Content-Type: application/json'
  ]);

  $response = curl_exec($ch);
  $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);

  if ($httpCode === 200) {
    error_log('AdRoll conversion tracked: ' . $response);
    return ['success' => true];
  } else {
    error_log('AdRoll API error: ' . $response);
    return ['success' => false, 'error' => $response];
  }
}

// After order is completed
$order = processCheckout($_POST);

trackAdRollConversion(
  [
    'order_id' => $order['id'],
    'customer_email' => $order['email'],
    'total' => $order['total'],
    'currency' => $order['currency'],
    'items' => $order['line_items']
  ],
  [
    'ip_address' => $_SERVER['REMOTE_ADDR'],
    'user_agent' => $_SERVER['HTTP_USER_AGENT']
  ]
);
?>

Advantages

1. Ad blocker immunity:

Server-side tracking bypasses:
✓ Browser ad blockers (uBlock, AdBlock Plus)
✓ Safari ITP cookie restrictions
✓ Firefox Enhanced Tracking Protection
✓ DNS-based blocking (Pi-hole)

Result: 100% event capture (vs. 60-70% client-side)

2. Data accuracy:

  • All conversions tracked (no browser blocking)
  • No JavaScript errors or network failures
  • Guaranteed event delivery

3. Privacy control:

// Full control over what data is sent
// Hash PII before sending
user_id: hashEmail(customer.email)

// Only send necessary fields
// Omit sensitive data (credit cards, SSNs)

4. Reduced client-side load:

  • No JavaScript to download/execute
  • Faster page loads
  • Better mobile performance

Disadvantages

1. Implementation complexity:

Requires:
- Backend code changes
- API key management
- Error handling
- Manual user identification
- IP/user agent passing

2. User identification challenges:

// Client-side: Automatic cookie-based ID
// Server-side: Must manually track user

// Options:
1. Hash email (requires email collection)
2. Pass cookie ID from client to server
3. Use database user ID (AdRoll may not recognize)

3. Missing automatic context:

// Client-side automatically captures:
- Referrer URL
- Page URL
- Session data
- Device/browser info

// Server-side: Must manually collect and pass
user_data: {
  ip_address: req.ip,           // Manual
  user_agent: req.headers['ua'], // Manual
  referrer: req.headers['ref']   // Manual
}

4. Delayed pageview tracking:

Client-side: Tracks every pageview automatically
Server-side: Only tracks events you explicitly send

→ Server-side alone doesn't build "all visitors" audience
→ Need client-side pixel for pageview audience building

Why Hybrid?

Combine client-side and server-side for best of both:

  • Client-side: Pageviews, audience building, automatic context
  • Server-side: Conversions, bypassing ad blockers, privacy control

Coverage comparison:

Client-side only:  60-70% conversion capture (ad blockers)
Server-side only:  100% conversions, but no pageview audiences
Hybrid:            100% conversions + full audience building

Implementation Strategy

1. Client-side pixel for pageviews:

<!-- Install pixel on all pages for audience building -->
<script type="text/javascript">
  adroll_adv_id = "ABC123XYZ";
  adroll_pix_id = "DEF456GHI";
  /* ... pixel code ... */
</script>

<script>
  // Track product pageviews
  adroll.track("pageView", {
    product_id: "SKU-001",
    price: 49.99
  });
</script>

2. Server-side API for conversions:

// On checkout completion (server-side)
await trackAdRollConversion({
  order_id: "ORD-123",
  conversion_value: 99.99,
  currency: "USD"
});

3. Deduplication strategy:

Prevent counting same conversion twice (client + server):

// CLIENT-SIDE: Generate unique event ID
const eventId = 'evt_' + Date.now() + '_' + Math.random().toString(36);

// Send to AdRoll
adroll.track("purchase", {
  order_id: "ORD-123",
  conversion_value: 99.99,
  currency: "USD",
  event_id: eventId // Deduplication key
});

// Also send event_id to your server
fetch('/api/track-conversion', {
  method: 'POST',
  body: JSON.stringify({
    order_id: "ORD-123",
    event_id: eventId // Same ID
  })
});

// SERVER-SIDE: Use same event_id in API call
trackAdRollConversion({
  order_id: "ORD-123",
  conversion_value: 99.99,
  event_id: eventId, // SAME ID as client
  // AdRoll deduplicates based on event_id
});

Deduplication behavior:

Client-side fires first:  event_id = "evt_123"
Server-side fires 2s later: event_id = "evt_123" (same)

AdRoll result: 1 conversion (deduplicated)

Hybrid Architecture Example

// === CLIENT-SIDE (browser) ===

// 1. Load pixel for pageviews
<script src="adroll-pixel.js"></script>

// 2. Track product views automatically
adroll.track("pageView", {
  product_id: currentProduct.id,
  price: currentProduct.price
});

// 3. On checkout: Track client-side with event_id
async function completeCheckout(orderData) {
  const eventId = generateEventId();

  // Send to AdRoll (client-side)
  adroll.track("purchase", {
    order_id: orderData.id,
    conversion_value: orderData.total,
    event_id: eventId
  });

  // Send to server (for server-side tracking)
  await fetch('/api/complete-order', {
    method: 'POST',
    body: JSON.stringify({
      ...orderData,
      adroll_event_id: eventId // Pass event_id to server
    })
  });
}

// === SERVER-SIDE (backend) ===

app.post('/api/complete-order', async (req, res) => {
  const order = req.body;

  // 1. Save order to database
  await db.orders.create(order);

  // 2. Track server-side with SAME event_id
  await trackAdRollConversion({
    order_id: order.id,
    conversion_value: order.total,
    currency: order.currency,
    event_id: order.adroll_event_id, // Same ID as client
    user_id: hashEmail(order.customer_email),
    user_data: {
      ip_address: req.ip,
      user_agent: req.headers['user-agent']
    }
  });

  res.json({ success: true });
});

Result:

  • If client-side succeeds: Conversion tracked immediately
  • If client-side blocked: Server-side ensures conversion tracked
  • If both succeed: AdRoll deduplicates via event_id
  • Audience building: Client-side pixel builds audiences from pageviews

User Identification Strategies

Challenge

Server-side API requires user_id to attribute events:

// AdRoll needs to know: "Which user did this?"
user_id: "???" // What to use?

Option 1: Hashed Email (Best for Conversions)

// Use SHA-256 hashed email as user_id
const crypto = require('crypto');

function hashEmail(email) {
  return crypto.createHash('sha256')
    .update(email.toLowerCase().trim())
    .digest('hex');
}

// Track conversion with hashed email
trackAdRollConversion({
  user_id: hashEmail('customer@example.com'),
  event: 'purchase',
  // ...
});

Pros:

  • Works across devices (same email, same ID)
  • Privacy-friendly (hashed, not plain text)
  • Matches AdRoll's email-based audiences

Cons:

  • Only works when you have email (post-conversion)
  • Can't track anonymous pageviews
// CLIENT-SIDE: Get AdRoll's cookie ID
function getAdRollUserId() {
  const cookies = document.cookie.split(';');
  for (let cookie of cookies) {
    const [name, value] = cookie.trim().split('=');
    if (name === '__adroll_fpc') {
      return value; // AdRoll's user ID
    }
  }
  return null;
}

// Send to server with checkout request
const adrollUserId = getAdRollUserId();
fetch('/api/checkout', {
  method: 'POST',
  body: JSON.stringify({
    cart: cartData,
    adroll_user_id: adrollUserId // Pass to server
  })
});

// SERVER-SIDE: Use AdRoll's cookie ID
app.post('/api/checkout', async (req, res) => {
  trackAdRollConversion({
    user_id: req.body.adroll_user_id, // From client
    event: 'purchase',
    // ...
  });
});

Pros:

  • Matches AdRoll's existing user tracking
  • Works for anonymous users (no email needed)
  • Seamless hybrid integration

Cons:

  • Requires client-side pixel to set cookie first
  • Blocked if user has ad blockers (no cookie set)

Option 3: Database User ID (Fallback)

// Use your own user ID as fallback
trackAdRollConversion({
  user_id: `db_user_${userId}`, // Prefix to avoid conflicts
  event: 'purchase',
  // ...
});

Pros:

  • Always available (from your database)
  • Works offline or without browser

Cons:

  • AdRoll may not recognize ID (can't match to existing audiences)
  • Doesn't sync with client-side pixel users
// Cascading user ID strategy (best to worst)

function getAdRollUserId(user) {
  // 1. Try AdRoll cookie ID (best for hybrid)
  if (user.adroll_cookie_id) {
    return user.adroll_cookie_id;
  }

  // 2. Use hashed email (good for conversions)
  if (user.email) {
    return hashEmail(user.email);
  }

  // 3. Fallback to database ID (last resort)
  return `db_user_${user.id}`;
}

When to Use Each Approach

Use Client-Side Only When:

  • ✓ Simple website with basic tracking needs
  • ✓ E-commerce platform with app integration (Shopify, WooCommerce)
  • ✓ No development resources for backend integration
  • ✓ Ad blocker impact acceptable (30-40% data loss okay)
  • ✓ Budget or time constraints prevent server-side build

Use Server-Side Only When:

  • ✓ Mobile app (no browser/JavaScript)
  • ✓ Backend-only processing (no frontend)
  • ✓ Maximum data accuracy required
  • ✓ Privacy regulations require server-side control
  • ✓ High-value conversions (can't afford to miss any)

Use Hybrid When:

  • Best accuracy needed (both pageviews and conversions)
  • ✓ Want to bypass ad blockers for conversions
  • ✓ Need audience building AND guaranteed conversion tracking
  • ✓ Have development resources for backend integration
  • Recommended for most e-commerce implementations

API Reference

Authentication

Get API key from AdRoll dashboard:

  1. Settings → API Access → Create API Key
  2. Copy key: Bearer adroll_api_xxxxxxxxxxxxx
  3. Store securely (environment variable, secrets manager)

Usage:

curl -X POST https://services.adroll.com/api/v1/track \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"advertiser_id": "ABC123", ...}'

Event Types

Purchase:

{
  "event": "purchase",
  "properties": {
    "order_id": "ORD-123",
    "conversion_value": 99.99,
    "currency": "USD",
    "products": [...]
  }
}

Lead:

{
  "event": "lead",
  "properties": {
    "segment_name": "contact_leads",
    "conversion_value": 50
  }
}

Page View:

{
  "event": "pageView",
  "properties": {
    "product_id": "SKU-001",
    "price": 49.99,
    "url": "https://example.com/products/widget"
  }
}

Add to Cart:

{
  "event": "addToCart",
  "properties": {
    "product_id": "SKU-001",
    "quantity": 1,
    "price": 49.99
  }
}

Rate Limits

  • 100 requests per minute per API key
  • 10,000 requests per day per advertiser
  • Exceeding limits returns 429 Too Many Requests

Implement retry logic:

async function trackWithRetry(data, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await axios.post(API_URL, data, { headers });
      return response.data;
    } catch (error) {
      if (error.response?.status === 429) {
        // Rate limited, wait and retry
        await sleep(2 ** i * 1000); // Exponential backoff
        continue;
      }
      throw error; // Other errors, don't retry
    }
  }
  throw new Error('Max retries exceeded');
}

Testing & Validation

Test Server-Side API

Manual test with curl:

curl -X POST https://services.adroll.com/api/v1/track \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "advertiser_id": "ABC123XYZ",
    "pixel_id": "DEF456GHI",
    "event": "purchase",
    "user_id": "test_user_123",
    "event_id": "test_event_'$(date +%s)'",
    "properties": {
      "order_id": "TEST-'$(date +%s)'",
      "conversion_value": 10.00,
      "currency": "USD"
    },
    "timestamp": '$(date +%s)'
  }'

Expected response:

{
  "success": true,
  "event_id": "test_event_1640000000"
}

Verify in AdRoll Dashboard

  1. AdRoll → Reports → Conversions
  2. Filter by recent date
  3. Look for test order ID
  4. May take 24-48 hours to appear

Validate Deduplication

// Send same event twice with same event_id
const eventId = 'dedup_test_' + Date.now();

// First call
await trackAdRollConversion({
  event_id: eventId,
  order_id: "TEST-001",
  conversion_value: 10.00
});

// Second call (should be deduplicated)
await trackAdRollConversion({
  event_id: eventId, // SAME ID
  order_id: "TEST-001",
  conversion_value: 10.00
});

// Check dashboard: Should see 1 conversion, not 2

Best Practices

Security

1. Store API keys securely:

// WRONG - Hardcoded API key
const API_KEY = 'adroll_api_abc123xyz';

// CORRECT - Environment variable
const API_KEY = process.env.ADROLL_API_KEY;

// BEST - Secrets manager (AWS Secrets Manager, Vault)
const API_KEY = await secretsManager.getSecret('adroll_api_key');

2. Hash PII before sending:

// Hash email, don't send plain text
user_id: hashEmail(customer.email)

// Never send credit cards or SSNs
// ❌ properties: { credit_card: '4111...' }

3. Validate data before sending:

function validateConversion(data) {
  if (!data.order_id) throw new Error('order_id required');
  if (typeof data.conversion_value !== 'number') throw new Error('conversion_value must be number');
  if (!['USD', 'EUR', 'GBP'].includes(data.currency)) throw new Error('Invalid currency');
  // ... more validation
}

Error Handling

async function trackAdRollConversion(data) {
  try {
    const response = await axios.post(API_URL, data, { headers });
    console.log('✓ AdRoll conversion tracked');
    return { success: true };
  } catch (error) {
    // Log error but don't break checkout
    console.error('AdRoll API error:', error.response?.data || error.message);

    // Send to error monitoring (Sentry, Rollbar)
    errorMonitor.capture(error, { context: 'adroll_tracking' });

    // Continue with order processing
    return { success: false, error: error.message };
  }
}

// Don't let AdRoll tracking failure break checkout
const order = await createOrder(data);
await trackAdRollConversion(order); // Async, non-blocking
return { orderId: order.id }; // Return success regardless

Performance Optimization

1. Fire and forget (don't await):

// SLOW - Waits for AdRoll response before returning
await trackAdRollConversion(order);
res.json({ orderId: order.id });

// FAST - Fire tracking in background
trackAdRollConversion(order); // No await
res.json({ orderId: order.id }); // Return immediately

2. Queue for batch processing:

// Add to queue instead of real-time API call
await queue.add('adroll-conversion', {
  order_id: order.id,
  // ... data
});

// Worker processes queue in background
queue.process('adroll-conversion', async (job) => {
  await trackAdRollConversion(job.data);
});

Next Steps

// SYS.FOOTER