FullStory Server-Side vs Client-Side | OpsBlu Docs

FullStory Server-Side vs Client-Side

Understand the differences between client-side and server-side tracking approaches for FullStory implementation.

Overview

FullStory is primarily a client-side analytics and session replay platform, meaning it captures user interactions directly in the browser. However, understanding when and how to complement FullStory with server-side data is important for a complete analytics strategy.

This guide explains the differences, use cases, and how to combine both approaches effectively.

Client-Side Tracking (Default)

What It Is

Client-side tracking means FullStory runs in the user's browser, capturing:

  • Page views and navigation
  • Mouse movements, clicks, scrolls
  • Form interactions and inputs
  • JavaScript errors and console logs
  • DOM snapshots for session replay

How It Works

  1. FullStory JavaScript snippet loads in the browser
  2. SDK initializes and starts recording
  3. User interactions are captured in real-time
  4. Data is buffered locally and sent to FullStory servers
  5. Sessions become available in the dashboard within minutes

Advantages

Complete user experience capture:

  • See exactly what users see and do
  • Pixel-perfect session replay
  • Rage clicks, dead clicks, error clicks
  • User frustration signals

No server infrastructure needed:

Rich behavioral data:

  • Mouse movements and hover patterns
  • Scroll depth and engagement
  • Form field interactions
  • Real-time user journey mapping

Limitations

Client-side only:

  • Doesn't capture server-side logic or API responses
  • Can't track what happens on the backend
  • Limited visibility into server errors or performance

Browser dependency:

  • Requires JavaScript enabled
  • Can be blocked by ad blockers or privacy extensions
  • Doesn't work for server-rendered emails or PDFs

Privacy constraints:

  • Can't capture data from cross-origin iframes
  • Subject to browser security policies (CSP, CORS)

When to Use Client-Side Only

Client-side tracking is sufficient when:

  • You primarily care about user experience and behavior
  • Your application is a web or mobile app
  • You want session replay and visual insights
  • You don't need deep server-side correlation

Server-Side Tracking

What It Is

Server-side tracking means sending data to FullStory from your backend servers, typically via API calls. FullStory supports limited server-side tracking for user identification and custom events.

FullStory Server-Side API

FullStory provides a server-side API for:

  • User identification: Associate server-known user IDs with FullStory sessions
  • Custom events: Send server-side events (e.g., payment processed, API called)

Note: FullStory's server-side API is not for session replay. Session replay requires client-side JavaScript.

Server-Side Event Tracking

Use FullStory's HTTP API to send events from your backend:

Endpoint:

POST https://api.fullstory.com/v2/events

Headers:

Authorization: Basic {API_KEY}
Content-Type: application/json

Payload:

{
  "events": [
    {
      "name": "Payment Processed",
      "timestamp": "2024-01-15T12:34:56Z",
      "userId": "user_12345",
      "properties": {
        "amount": 149.99,
        "currency": "USD",
        "payment_method": "credit_card"
      }
    }
  ]
}

Example (Node.js):

const axios = require('axios');

async function trackServerEvent(eventName, userId, properties) {
  const apiKey = process.env.FULLSTORY_API_KEY;
  const url = 'https://api.fullstory.com/v2/events';

  const payload = {
    events: [{
      name: eventName,
      timestamp: new Date().toISOString(),
      userId: userId,
      properties: properties
    }]
  };

  try {
    await axios.post(url, payload, {
      headers: {
        'Authorization': `Basic ${Buffer.from(apiKey + ':').toString('base64')}`,
        'Content-Type': 'application/json'
      }
    });
    console.log('Event tracked successfully');
  } catch (error) {
    console.error('Failed to track event:', error);
  }
}

// Usage
trackServerEvent('Payment Processed', 'user_12345', {
  amount: 149.99,
  currency: 'USD',
  payment_method: 'credit_card'
});

Advantages of Server-Side Tracking

Reliable event delivery:

  • Not affected by ad blockers or browser extensions
  • Guaranteed delivery (server-controlled)

Capture backend events:

  • Payment processing
  • Subscription changes
  • API calls and integrations
  • Cron jobs and scheduled tasks

No client-side dependency:

  • Track events even if user leaves the page
  • Works for non-browser contexts (email opens, API usage)

Limitations

No session replay:

  • Server-side API doesn't capture visual sessions
  • Can't see user interactions or DOM state

More complex implementation:

  • Requires backend code changes
  • Need to manage API keys securely
  • Must correlate server events with client sessions

Potential for duplicate events:

  • If not careful, you might track the same event both client-side and server-side

For most applications, a hybrid approach combining client-side and server-side tracking is ideal.

Use Client-Side For:

  • User experience tracking:

    • Session replay
    • Heatmaps and click maps
    • User journeys
    • Rage clicks and frustration signals
  • Frontend events:

    • Button clicks
    • Form interactions
    • Page views
    • JavaScript errors
  • User identification:

    • Identify users via FS.identify() when they log in

Use Server-Side For:

  • Backend events that don't happen in the browser:

    • Payment processing (after Stripe/PayPal confirms)
    • Subscription renewals (cron jobs)
    • Email sent confirmations
    • API integrations (Salesforce sync, etc.)
  • Events after user leaves the page:

    • Webhook callbacks
    • Async processing completion
  • Reliable business-critical events:

    • Revenue events (can't risk ad blocker interference)

Example Hybrid Implementation

Client-side (browser):

// Track button click
document.getElementById('checkout-btn').addEventListener('click', function() {
  FS.event('Checkout Button Clicked', {
    cart_value: cart.total,
    items: cart.items.length
  });
});

// Identify user on login
function onUserLogin(user) {
  FS.identify(user.id, {
    email_str: user.email,
    plan_str: user.plan
  });
}

Server-side (Node.js):

// After payment is processed on the server
async function onPaymentSuccess(payment) {
  // Send to FullStory server-side API
  await trackServerEvent('Payment Processed', payment.userId, {
    amount: payment.amount,
    currency: payment.currency,
    payment_method: payment.method,
    order_id: payment.orderId
  });

  // Also send confirmation email, update database, etc.
}

Result:

  • FullStory shows "Checkout Button Clicked" from client-side
  • FullStory shows "Payment Processed" from server-side
  • Both events are linked to the same user session via userId

Correlating Client and Server Events

To link client-side sessions with server-side events, use consistent user IDs:

1. Client-side identification:

FS.identify('user_12345', {
  email_str: 'user@example.com'
});

2. Server-side event tracking:

trackServerEvent('Subscription Renewed', 'user_12345', {
  plan: 'premium',
  renewal_date: '2024-02-15'
});

3. FullStory links them together:

When you search for user_12345 in FullStory, you'll see:

  • Client-side session replays
  • Client-side events (button clicks, form submissions)
  • Server-side events (payments, subscriptions)

All correlated to the same user.

Best Practices

Always Use Client-Side for User Experience

Session replay and behavioral insights require client-side JavaScript. Don't try to replicate this server-side.

Use Server-Side for Critical Business Events

Revenue events, payment processing, and subscription changes should be tracked server-side to ensure reliability.

Avoid Duplicate Tracking

Don't track the same event both client-side and server-side unless you have a specific reason:

// Bad: Duplicate tracking
// Client-side
FS.event('Purchase Completed', { amount: 99.99 });

// Server-side (also tracking the same event)
trackServerEvent('Purchase Completed', userId, { amount: 99.99 });

// Good: Track different stages
// Client-side: User intent
FS.event('Checkout Button Clicked');

// Server-side: Actual outcome
trackServerEvent('Payment Processed', userId, { amount: 99.99 });

Use Consistent User IDs

Ensure the same user ID is used across client-side and server-side tracking:

const userId = user.id;  // From your database

// Client-side
FS.identify(userId, { email_str: user.email });

// Server-side
trackServerEvent('Event Name', userId, {});

Secure Server-Side API Keys

Never expose your FullStory API key in client-side code:

// Bad: API key exposed in browser
FS.event('Secret Event', { apiKey: 'fs_api_key_123' });

// Good: API key only on server
// Server-side only, never in browser code
const apiKey = process.env.FULLSTORY_API_KEY;

When to Skip Server-Side

You may not need server-side tracking if:

  • Your application is entirely client-side (SPA with no critical backend events)
  • You only care about user experience and behavior (not backend events)
  • Your backend events are already visible to the client (e.g., API responses)

In these cases, client-side tracking alone is sufficient.

Summary

Aspect Client-Side Server-Side
Session Replay Yes No
Heatmaps Yes No
User Behavior Yes No
Backend Events No Yes
Ad Blocker Proof No Yes
Payment Events Unreliable Reliable
Requires Backend Code No Yes
Setup Complexity Low Medium

Recommendation: Use client-side as your primary tracking method for user experience insights. Add server-side tracking for critical backend events like payments, subscriptions, and API integrations.

Troubleshooting

Common Server-Side Issues

Issue Symptoms Possible Causes Solutions
Server-side events not appearing API call succeeds but events missing in FullStory - Invalid API key
- Incorrect Org ID in API request
- User ID doesn't match client-side sessions
- Event name validation failure
- Verify API key is correct and active
- Confirm Org ID matches dashboard
- Use same user ID for client and server
- Check event name follows naming rules
401 Unauthorized error API returns 401 status - API key invalid or expired
- Incorrect authorization header format
- API key not base64 encoded properly
- Regenerate API key in FullStory dashboard
- Use Basic {base64(apiKey:)} format
- Verify base64 encoding includes trailing colon
Events not linked to sessions Server events appear but isolated from client sessions - Different user IDs used
- User not identified client-side
- Timing mismatch
- Use consistent user ID across client/server
- Call FS.identify() on client before server events
- Ensure user ID is string on both sides
429 Rate limit error Too many requests error - Exceeding API rate limits
- Sending events in tight loop
- No rate limiting implemented
- Implement exponential backoff
- Batch events when possible
- Add rate limiting to your code
- Contact FullStory to adjust limits
Client-side events missing Ad blockers, privacy tools blocking - Ad blocker installed
- Privacy browser extensions
- Corporate firewall
- No perfect solution (browser limitation)
- Consider server-side for critical events
- Track ad blocker detection rate
Hybrid correlation issues Can't connect client and server events for same user - User ID mismatch
- Client identify not called
- Server events fire before client session
- Standardize user ID format
- Identify users on login immediately
- Delay server events until client session established
API payload errors 400 Bad Request errors - Invalid JSON in payload
- Missing required fields
- Property types incorrect
- Timestamp format wrong
- Validate JSON before sending
- Include name, userId, timestamp
- Add proper type suffixes to properties
- Use ISO 8601 format for timestamps
Duplicate events Same event tracked twice (client + server) - Both client and server tracking same action
- No deduplication logic
- Track different stages (client: intent, server: outcome)
- Only use server-side for backend-only events
- Implement deduplication IDs
Performance degradation Server-side tracking slowing requests - Synchronous API calls blocking requests
- No timeout set on API calls
- Not using async/background processing
- Use async HTTP requests
- Set reasonable timeouts (2-5 seconds)
- Queue events for background processing
- Don't block critical paths
Missing event properties Events appear but properties empty - Properties not included in API payload
- Type suffixes missing
- Property values null/undefined
- Include properties object in payload
- Add _str, _int, etc. suffixes
- Validate property values before sending

Client-Side Troubleshooting

FullStory script not loading:

// Check if FullStory loaded
if (typeof FS === 'undefined') {
  console.error('FullStory not loaded');
  // Check Network tab for failed requests
  // Check for ad blocker interference
}

Session not recording:

// Check if recording is active
const sessionURL = FS.getCurrentSessionURL();
if (!sessionURL) {
  console.warn('No active recording session');
  // Possible causes:
  // - Sampling excluded this session
  // - Recording explicitly shut down
  // - Privacy settings blocking
}

Identify not working:

// Verify user identification
FS.identify('user_123', {
  email_str: 'user@example.com',
  plan_str: 'premium'
});

// Then check session URL includes user ID
console.log(FS.getCurrentSessionURL());

Server-Side API Debugging

Test API connectivity:

# Test API with curl
curl -X POST https://api.fullstory.com/v2/events \
  -H "Authorization: Basic $(echo -n 'YOUR_API_KEY:' | base64)" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [{
      "name": "Test Event",
      "timestamp": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'",
      "userId": "test_user_123",
      "properties": {
        "test_str": "value"
      }
    }]
  }'

Expected response:

{
  "status": "success",
  "eventsProcessed": 1
}

Log API requests:

// Node.js example
async function trackServerEvent(eventName, userId, properties) {
  const payload = {
    events: [{
      name: eventName,
      timestamp: new Date().toISOString(),
      userId: userId,
      properties: properties
    }]
  };

  console.log('[FullStory API] Request:', JSON.stringify(payload, null, 2));

  try {
    const response = await axios.post(
      'https://api.fullstory.com/v2/events',
      payload,
      {
        headers: {
          'Authorization': `Basic ${Buffer.from(apiKey + ':').toString('base64')}`,
          'Content-Type': 'application/json'
        }
      }
    );

    console.log('[FullStory API] Response:', response.data);
  } catch (error) {
    console.error('[FullStory API] Error:', error.response?.data || error.message);
    throw error;
  }
}

Validation Procedures

Test client-side tracking:

  1. Load your site
  2. Open DevTools console
  3. Execute: FS.getCurrentSessionURL()
  4. Should return session URL
  5. Trigger an event
  6. Verify event in session replay

Test server-side tracking:

  1. Make API call with test event
  2. Note the user ID used
  3. Search FullStory for that user ID
  4. Verify event appears in user's timeline
  5. Check event properties are correct

Test hybrid correlation:

  1. Load site (client-side identifies user)
  2. Trigger server-side event for same user
  3. View session in FullStory
  4. Both client and server events should appear in same session

Checklist:

  • Client-side script loads successfully
  • FS.identify() called with user ID
  • Server-side API returns 200 status
  • Server events use same user ID format
  • Events appear in FullStory within minutes
  • Client and server events linked to same user
  • No duplicate events
  • Critical events use server-side (reliable)
  • UX events use client-side (rich context)

Best Practices Summary

Client-Side:

  • Use for all user experience tracking
  • Implement on page load
  • Handle FullStory not loading gracefully
  • Don't block page rendering

Server-Side:

  • Use for critical business events only
  • Implement async/background processing
  • Set timeouts on API calls
  • Log all API requests/responses
  • Handle failures gracefully (retry logic)

Hybrid:

  • Use consistent user IDs
  • Identify users client-side first
  • Track different stages (client: attempt, server: success)
  • Avoid duplicate tracking
  • Document which events use which method

Common Mistakes to Avoid

Blocking critical paths:

// Bad - Blocks user request
app.post('/api/checkout', async (req, res) => {
  const result = await processCheckout(req.body);

  // This blocks the response
  await trackServerEvent('Purchase', userId, result);

  res.json(result);
});

// Good - Async tracking
app.post('/api/checkout', async (req, res) => {
  const result = await processCheckout(req.body);

  // Fire and forget (or queue for background processing)
  trackServerEvent('Purchase', userId, result).catch(err =>
    console.error('FullStory tracking failed:', err)
  );

  res.json(result);  // Respond immediately
});

Inconsistent user IDs:

// Bad - Different formats
// Client-side
FS.identify('12345', {...});

// Server-side
trackServerEvent('Event', 'user_12345', {...});  // Won't link!

// Good - Same format everywhere
const userId = 'user_12345';

// Client-side
FS.identify(userId, {...});

// Server-side
trackServerEvent('Event', userId, {...});

Missing error handling:

// Bad - No error handling
await trackServerEvent('Event', userId, props);

// Good - Graceful degradation
try {
  await trackServerEvent('Event', userId, props);
} catch (error) {
  console.error('FullStory tracking failed:', error);
  // Don't throw - allow app to continue
  // Optionally: queue for retry
}

Getting Help

Client-Side Issues:

Server-Side Issues:

Support Contact:


Next Steps:

Additional Resources: