Pendo Tracking Approaches | OpsBlu Docs

Pendo Tracking Approaches

Compare server-side and client-side tracking approaches in Pendo — performance, accuracy, and implementation trade-offs.

Tracking Approaches

Pendo offers both client-side and server-side tracking approaches, each designed for specific use cases. Understanding the differences and when to use each method will help you implement Pendo effectively.

Client-Side Tracking

Client-side tracking is Pendo's primary implementation method, using JavaScript that runs directly in the user's browser.

What Client-Side Tracks

Automatic Tracking

  • Page views and navigation
  • Feature clicks and interactions
  • Form submissions
  • Session duration and user activity
  • Element visibility and engagement
  • Browser and device information

Manual Tracking

// Custom events
pendo.track('Feature Used', {
  feature_name: 'Advanced Search',
  user_action: 'filter_applied'
});

// User identification
pendo.identify({
  visitor: {
    id: 'user-123',
    email: 'user@example.com'
  }
});

Client-Side Capabilities

In-App Guides and Messages

One of Pendo's most powerful features, only available client-side:

// Guides automatically appear based on rules set in Pendo UI
// No additional code needed beyond initialization

// Programmatically control guides
pendo.showGuideById('guide-id');
pendo.dismissActiveGuides();

Visual Design and Tagging

The Pendo Visual Designer allows non-technical users to:

  • Tag features without code
  • Create guides visually
  • Define page rules
  • Set up click tracking
// Make elements easier to tag
<button data-pendo="checkout-button">
  Checkout
</button>

Session Replay

Pendo captures user sessions for replay and analysis:

// Session replay happens automatically when enabled
// Configure in Pendo settings

// Control recording programmatically
pendo.stopSendingEvents(); // Pause recording
pendo.startSendingEvents(); // Resume recording

Client-Side Implementation

Basic Setup

<script>
  (function(apiKey){
    (function(p,e,n,d,o){var v,w,x,y,z;o=p[d]=p[d]||{};o._q=o._q||[];
    v=['initialize','identify','updateOptions','pageLoad','track'];for(w=0,x=v.length;w<x;++w)(function(m){
        o[m]=o[m]||function(){o._q[m===v[0]?'unshift':'push']([m].concat([].slice.call(arguments,0)));};})(v[w]);
        y=e.createElement(n);y.async=!0;y.src='https://cdn.pendo.io/agent/static/'+apiKey+'/pendo.js';
        z=e.getElementsByTagName(n)[0];z.parentNode.insertBefore(y,z);})(window,document,'script','pendo');
  })('YOUR-API-KEY');
</script>

Full Initialization

pendo.initialize({
  visitor: {
    id: 'USER_ID',
    email: 'user@example.com',
    full_name: 'John Doe',
    role: 'admin'
  },
  account: {
    id: 'ACCOUNT_ID',
    name: 'Acme Corp',
    plan: 'enterprise'
  }
});

Client-Side Advantages

  • Rich user experience: In-app guides, tooltips, and walkthroughs
  • Visual features: Session replay, heatmaps, and click tracking
  • No backend changes: Quick to implement without server modifications
  • Real-time feedback: Immediate user engagement and feedback collection
  • Contextual help: Display guides based on user behavior and page context

Client-Side Limitations

  • Browser dependency: Requires JavaScript enabled
  • Ad blockers: May be blocked by privacy extensions
  • Page load dependency: Can't track events before page loads
  • Client-side only data: Limited to browser-accessible information
  • Performance impact: Minimal but adds to page load size

Server-Side Integration

While Pendo is primarily client-side, it offers server-side capabilities through its API for specific use cases.

When to Use Server-Side

Backend Events

// Node.js example
const axios = require('axios');

async function sendPendoEvent(visitorId, accountId, eventName, properties) {
  await axios.post('https://app.pendo.io/data/track', {
    type: 'track',
    event: eventName,
    visitorId: visitorId,
    accountId: accountId,
    properties: properties,
    timestamp: Date.now()
  }, {
    headers: {
      'X-Pendo-Integration-Key': 'YOUR-INTEGRATION-KEY',
      'Content-Type': 'application/json'
    }
  });
}

// Track server-side event
await sendPendoEvent(
  'user-123',
  'account-456',
  'Payment Processed',
  {
    amount: 99.99,
    currency: 'USD',
    payment_method: 'credit_card'
  }
);

Data Synchronization

// Sync user data from your database to Pendo
async function syncUserToPendo(user) {
  await axios.post('https://app.pendo.io/api/v1/metadata/visitor/value', {
    values: [{
      visitorId: user.id,
      values: {
        email: user.email,
        role: user.role,
        created_at: user.createdAt,
        last_login: user.lastLogin,
        total_purchases: user.totalPurchases
      }
    }]
  }, {
    headers: {
      'X-Pendo-Integration-Key': 'YOUR-INTEGRATION-KEY',
      'Content-Type': 'application/json'
    }
  });
}

Pendo Integration API

The Integration API allows server-side data management:

Visitor Metadata

# Python example
import requests

def update_visitor_metadata(visitor_id, metadata):
    url = 'https://app.pendo.io/api/v1/metadata/visitor/value'
    headers = {
        'X-Pendo-Integration-Key': 'YOUR-INTEGRATION-KEY',
        'Content-Type': 'application/json'
    }
    payload = {
        'values': [{
            'visitorId': visitor_id,
            'values': metadata
        }]
    }

    response = requests.post(url, json=payload, headers=headers)
    return response.json()

# Update user role after server-side change
update_visitor_metadata('user-123', {
    'role': 'super_admin',
    'permissions': ['admin', 'billing', 'analytics']
})

Account Metadata

def update_account_metadata(account_id, metadata):
    url = 'https://app.pendo.io/api/v1/metadata/account/value'
    headers = {
        'X-Pendo-Integration-Key': 'YOUR-INTEGRATION-KEY',
        'Content-Type': 'application/json'
    }
    payload = {
        'values': [{
            'accountId': account_id,
            'values': metadata
        }]
    }

    response = requests.post(url, json=payload, headers=headers)
    return response.json()

# Update account after subscription change
update_account_metadata('account-456', {
    'plan': 'enterprise',
    'mrr': 999,
    'users_count': 50
})

Server-Side Use Cases

E-commerce Events

// Track purchase completion server-side
app.post('/api/purchase/complete', async (req, res) => {
  const { userId, accountId, order } = req.body;

  // Process payment
  const payment = await processPayment(order);

  // Send to Pendo
  await sendPendoEvent(userId, accountId, 'Purchase Completed', {
    order_id: order.id,
    revenue: order.total,
    items: order.items.length,
    payment_status: payment.status
  });

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

Batch Data Updates

// Nightly sync of user data
async function syncAllUsersToPendo() {
  const users = await db.users.findAll();

  for (const user of users) {
    await syncUserToPendo(user);
    await sleep(100); // Rate limiting
  }
}

// Run as cron job
cron.schedule('0 2 * * *', syncAllUsersToPendo);

Webhook Processing

// Handle webhook from payment provider
app.post('/webhooks/stripe', async (req, res) => {
  const event = req.body;

  if (event.type === 'subscription.updated') {
    const subscription = event.data.object;

    await update_account_metadata(subscription.metadata.account_id, {
      subscription_status: subscription.status,
      plan: subscription.plan.id,
      mrr: subscription.plan.amount / 100
    });
  }

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

Hybrid Approach

Most Pendo implementations use both client-side and server-side tracking:

Division of Responsibilities

Client-Side Handles:

  • User interface interactions
  • Page views and navigation
  • In-app guides and messages
  • Session replay
  • Real-time user engagement

Server-Side Handles:

  • Backend events (payments, API calls)
  • Data synchronization
  • Batch updates
  • Sensitive operations
  • System-to-system integrations

Example Hybrid Implementation

// Client-side: Track UI interaction
document.getElementById('upgrade-button').addEventListener('click', () => {
  pendo.track('Upgrade Button Clicked', {
    current_plan: user.plan,
    clicked_plan: 'enterprise'
  });

  // Initiate upgrade
  upgradeSubscription('enterprise');
});

// Server-side: Track actual upgrade
app.post('/api/subscription/upgrade', async (req, res) => {
  const { userId, accountId, newPlan } = req.body;

  // Process upgrade
  const result = await processUpgrade(userId, newPlan);

  // Send server-side event to Pendo
  await sendPendoEvent(userId, accountId, 'Subscription Upgraded', {
    from_plan: user.currentPlan,
    to_plan: newPlan,
    upgrade_success: result.success,
    new_mrr: result.newMRR
  });

  // Update account metadata
  await update_account_metadata(accountId, {
    plan: newPlan,
    mrr: result.newMRR
  });

  res.json(result);
});

Best Practices

Client-Side Best Practices

  1. Initialize early: Load Pendo as early as possible in page load
  2. Error handling: Wrap Pendo calls in try-catch blocks
  3. Privacy: Respect user privacy settings and consent
  4. Performance: Use async loading to avoid blocking page render
// Safe initialization
try {
  if (window.pendo && userConsent) {
    pendo.initialize(config);
  }
} catch (error) {
  console.error('Pendo initialization failed:', error);
}

Server-Side Best Practices

  1. Rate limiting: Respect API rate limits (typically 1000 requests/minute)
  2. Error handling: Handle API failures gracefully
  3. Batching: Batch updates when possible
  4. Security: Keep integration keys secure
// Rate-limited batch update
const queue = new RateLimitedQueue(100); // 100 requests/minute

async function batchUpdateUsers(users) {
  for (const user of users) {
    await queue.add(() => syncUserToPendo(user));
  }
}

Comparison Table

Feature Client-Side Server-Side
In-app guides Yes No
Session replay Yes No
Visual tagging Yes No
UI event tracking Yes No
Backend events Limited Yes
Data sync No Yes
Batch operations No Yes
Sensitive data Avoid Yes
Real-time Yes Yes
Ad blocker resistant No Yes

Additional Resources