Pendo Technical Implementation Guide | OpsBlu Docs

Pendo Technical Implementation Guide

Implement pendo.initialize(), configure visitor/account metadata, deploy in-app guides, and query the Pendo Data Explorer API.

How Pendo Works

Pendo operates through a JavaScript agent (the "Pendo Agent") that loads asynchronously into the host application. The agent performs three functions: behavioral data collection, in-app guide rendering, and feedback widget injection.

Data collection works through auto-capture. The agent attaches event listeners at the document level and records:

  • Page/route events: URL changes detected via pushState/replaceState interception and popstate listeners
  • Click events: Every click event on the page, resolved to a CSS selector path and the element's computed properties (text, href, id, class, data attributes)
  • Feature events: Clicks on elements that match tagged feature definitions (CSS selectors configured in the Pendo UI)

The agent does not record DOM snapshots or session replays by default (session replay is a separate add-on module). Instead, it captures structured event data: which features were used, on which pages, by which visitors, at what time.

Visitor and Account model: Pendo organizes data around two entities:

  • Visitor: An individual user, identified by a unique visitorId
  • Account: A company or organization, identified by an accountId

Every event is attributed to a visitor-account pair. This B2B-oriented data model enables account-level analytics (feature adoption by company, NPS by account tier, etc.).

Guide rendering: Pendo injects guide HTML/CSS into the host application's DOM. Guides are fetched from Pendo's CDN based on targeting rules evaluated client-side (visitor metadata, account metadata, URL, feature usage history, segment membership). Guide types include tooltips (anchored to a CSS selector), lightboxes (modal overlays), banners, and multi-step walkthroughs.

Data transmits to https://app.pendo.io/data/ via XHR POST. The agent compresses events into batched payloads sent every 5 minutes or on page unload.


Installing the Tracking Script

The Pendo agent snippet goes inside <head> or at the top of your application shell. Replace API_KEY with your Pendo subscription API key from Settings > Subscription Settings:

<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');
})('API_KEY');
</script>

After the snippet loads, initialize with visitor and account metadata:

pendo.initialize({
  visitor: {
    id: 'USER_123',
    email: 'jane@example.com',
    role: 'admin',
    plan: 'enterprise',
    creationDate: '2024-03-15T00:00:00Z'
  },
  account: {
    id: 'ACCT_456',
    name: 'Acme Corp',
    planLevel: 'enterprise',
    industry: 'SaaS',
    employeeCount: 250,
    arr: 120000
  }
});

For single-page applications, call pendo.initialize() once on app load. Pendo detects route changes automatically. If you need to force a page load event after a route change:

// After route transition in React/Vue/Angular
pendo.pageLoad();

For npm installations:

npm install @pendo/agent
import pendo from '@pendo/agent';

pendo.initialize({
  visitor: { id: currentUser.id, email: currentUser.email },
  account: { id: currentUser.accountId }
});

Content Security Policy:

script-src: https://cdn.pendo.io https://app.pendo.io
connect-src: https://app.pendo.io
img-src: https://cdn.pendo.io https://app.pendo.io https://pendo-static-*.storage.googleapis.com
style-src: 'unsafe-inline'
frame-src: https://app.pendo.io

Event Tracking and Data Collection

Auto-Captured Events

Pendo auto-captures two event types without any code:

Page events: Recorded on every URL change. Properties include the full URL, page title, and load time. In SPAs, triggered by pushState/replaceState.

Feature events: Recorded when a visitor clicks on an element matching a tagged feature's CSS selector. Features are defined in the Pendo UI using the visual tagging tool (point-and-click in your live application to select elements and assign feature names).

Custom Track Events

Send business logic events that do not correspond to page views or clickable features:

pendo.track('Upgrade Initiated', {
  currentPlan: 'starter',
  targetPlan: 'enterprise',
  source: 'settings_page'
});
pendo.track('Report Exported', {
  format: 'pdf',
  pageCount: 24,
  duration_ms: 3200
});

Track event properties can be strings, numbers, or booleans. Pendo does not support nested objects in track properties.

Feature Tagging

Feature tags are CSS selector rules defined in the Pendo UI. The agent evaluates click events against all active feature tag selectors. When a match occurs, the click is recorded as a feature event with the tag name.

For reliable tagging in applications with dynamic CSS class names, add stable identifiers:

<button data-pendo="upgrade-button" class="css-dynamic-hash">
  Upgrade Plan
</button>

Configure the feature tag in Pendo to target [data-pendo="upgrade-button"].

Updating Visitor/Account Metadata

Update metadata at any point during the session:

pendo.updateOptions({
  visitor: {
    id: 'USER_123',
    plan: 'enterprise',
    lastLoginDate: new Date().toISOString()
  },
  account: {
    id: 'ACCT_456',
    arr: 150000
  }
});

Metadata updates apply to all subsequent events in the session and update the visitor/account record in Pendo.


Identity and User Tracking

Visitor Identification

The visitor.id passed to pendo.initialize() is the primary identity key. Pendo creates or updates a visitor record keyed by this ID. All sessions, page events, feature events, guide interactions, and NPS responses are attributed to this visitor.

For anonymous visitors (pre-login), omit the visitor.id or pass an empty string. Pendo generates an anonymous ID stored in a cookie. After login, calling pendo.initialize() with the real visitor.id merges the anonymous session data.

// Before login (anonymous)
pendo.initialize({
  visitor: { id: '' },
  account: { id: '' }
});

// After login
pendo.initialize({
  visitor: { id: 'USER_123', email: 'jane@example.com' },
  account: { id: 'ACCT_456', name: 'Acme Corp' }
});

Account-Level Analytics

Every visitor belongs to an account. Pendo aggregates metrics at the account level:

  • Feature adoption by account (which accounts use which features)
  • NPS by account tier
  • Guide completion rates by account segment

Pass account.id consistently across all visitors in the same organization to enable account-level roll-ups.

Multi-Application Tracking

For organizations with multiple products, Pendo supports multiple subscription keys. Each application gets its own Pendo agent instance with a separate API key. Cross-application visitor matching is done by visitor.id if the same ID is used across applications.


API and Data Export

Aggregation API

Query visitor, account, feature, and page metrics:

# Get feature usage for a specific feature
curl -X GET \
  -H "X-Pendo-Integration-Key: YOUR_INTEGRATION_KEY" \
  -H "Content-Type: application/json" \
  "https://app.pendo.io/api/v1/feature/FEATURE_ID"

Data Explorer API (Aggregation Queries)

Run aggregation queries against Pendo data:

curl -X POST \
  -H "X-Pendo-Integration-Key: YOUR_INTEGRATION_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "response": {
      "mimeType": "application/json"
    },
    "request": {
      "pipeline": [
        {
          "source": {
            "featureEvents": {
              "featureId": "FEATURE_ID"
            }
          }
        },
        {
          "identified": "visitorId"
        },
        {
          "dateRange": {
            "first": "2025-10-01",
            "last": "2025-11-01"
          }
        },
        {
          "group": {
            "field": "visitorId"
          }
        },
        {
          "count": {}
        }
      ]
    }
  }' \
  "https://app.pendo.io/api/v1/aggregation"

This returns the number of times each visitor used the specified feature in the date range.

Visitor and Account APIs

# Get visitor details
curl -H "X-Pendo-Integration-Key: YOUR_INTEGRATION_KEY" \
  "https://app.pendo.io/api/v1/visitor/USER_123"

# List accounts with metadata
curl -H "X-Pendo-Integration-Key: YOUR_INTEGRATION_KEY" \
  "https://app.pendo.io/api/v1/account?limit=50&offset=0"

# Update account metadata via API
curl -X PUT \
  -H "X-Pendo-Integration-Key: YOUR_INTEGRATION_KEY" \
  -H "Content-Type: application/json" \
  -d '{"metadata": {"auto": {"arr": 200000}}}' \
  "https://app.pendo.io/api/v1/account/ACCT_456"

Guide API

# List all guides
curl -H "X-Pendo-Integration-Key: YOUR_INTEGRATION_KEY" \
  "https://app.pendo.io/api/v1/guide"

# Get guide analytics (views, completions, dismissals)
curl -H "X-Pendo-Integration-Key: YOUR_INTEGRATION_KEY" \
  "https://app.pendo.io/api/v1/guide/GUIDE_ID/stats"

Webhook Events

Pendo can push events to external endpoints:

{
  "event": "guideActivity",
  "data": {
    "guideId": "GUIDE_ID",
    "visitorId": "USER_123",
    "accountId": "ACCT_456",
    "type": "guideSeen",
    "stepId": "step_1",
    "timestamp": 1700000000000
  }
}

Common Issues

Feature tags not capturing clicks: The CSS selector in the feature tag does not match the rendered DOM element. Inspect the element in browser DevTools and compare the selector. Dynamic class names (CSS-in-JS) change on each build, breaking selector-based tags. Use data-pendo attributes for stable targeting.

pendo.initialize() called but no data appears: Verify the API key matches your subscription. Check the browser console for Pendo-related errors. Confirm that visitor.id is a non-empty string; passing undefined or null causes silent failures.

Guides not displaying: Guide targeting rules are evaluated client-side. Verify that the current visitor's metadata matches the guide's segment targeting. Check that the guide is published (not draft) and that the CSS selector for the anchor element exists in the current DOM. Open the Pendo Debugger (pendo.debugging.enableDebugMode()) to see why a guide was not shown.

Metadata not updating in reports: Pendo processes metadata asynchronously. Updates made via pendo.updateOptions() or the API may take up to 1 hour to reflect in reports and segments. Real-time guide targeting uses the most recent metadata immediately.

Duplicate visitors: If pendo.initialize() is called with different visitor.id values in the same session (e.g., identity changes without a page reload), Pendo creates separate visitor records. Call pendo.clearSession() before re-initializing with a different identity.

Agent size and load performance: The Pendo agent is approximately 100KB compressed. It loads asynchronously and does not block page rendering. Guide assets (images, custom CSS) are loaded on-demand only when a guide is triggered. For performance-sensitive pages, defer pendo.initialize() until after critical content loads.


Platform-Specific Considerations

Retroactive Analytics

Pendo's retroactive analytics capability works at the feature and page level. When you tag a new feature, Pendo applies the tag definition to all historical click data that matches the selector. This means you can define features after launch and immediately see adoption metrics going back to when the agent was first installed.

Retroactive analysis does not apply to custom pendo.track() events. Track events only produce data from the moment the code is deployed.

Guide Architecture

Guides inject HTML/CSS directly into the host application's DOM. They operate in the same CSS context as your application, which means:

  • Your application's CSS can affect guide styling (use specific Pendo CSS selectors to override)
  • Guides can be affected by z-index stacking contexts
  • Shadow DOM boundaries block guide rendering; Pendo cannot anchor tooltips inside Shadow DOM elements

Guide targeting evaluates on every page load and URL change. The agent checks: Does the current URL match? Does the visitor meet the segment criteria? Has the visitor already seen this guide (frequency rules)? If all conditions pass, the guide renders.

NPS Survey Implementation

Pendo NPS surveys are a specialized guide type. Configure in the Pendo UI with:

  • Targeting: which visitors/accounts see the survey
  • Frequency: how often the same visitor can be surveyed (e.g., once every 90 days)
  • Timing: delay after page load, trigger on specific URL, or trigger via API

Trigger an NPS survey programmatically:

pendo.showGuideById('NPS_GUIDE_ID');

NPS responses are stored in Pendo and accessible via the API:

curl -H "X-Pendo-Integration-Key: YOUR_INTEGRATION_KEY" \
  "https://app.pendo.io/api/v1/guide/NPS_GUIDE_ID/polls"

Data Residency and Privacy

Pendo offers US and EU data center options. Configure data residency during onboarding. The agent respects Do Not Track headers if enabled in Pendo settings. For GDPR compliance, use pendo.stopSendingEvents() to halt data collection and pendo.startSendingEvents() to resume after consent:

// Check consent status
if (!hasUserConsent()) {
  pendo.stopSendingEvents();
}

// After consent granted
pendo.startSendingEvents();

To delete a visitor's data for right-to-erasure requests, use the Visitor Deletion API:

curl -X DELETE \
  -H "X-Pendo-Integration-Key: YOUR_INTEGRATION_KEY" \
  "https://app.pendo.io/api/v1/visitor/USER_123"