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
- FullStory JavaScript snippet loads in the browser
- SDK initializes and starts recording
- User interactions are captured in real-time
- Data is buffered locally and sent to FullStory servers
- 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
Hybrid Approach (Recommended)
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
- Identify users via
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:
- Load your site
- Open DevTools console
- Execute:
FS.getCurrentSessionURL() - Should return session URL
- Trigger an event
- Verify event in session replay
Test server-side tracking:
- Make API call with test event
- Note the user ID used
- Search FullStory for that user ID
- Verify event appears in user's timeline
- Check event properties are correct
Test hybrid correlation:
- Load site (client-side identifies user)
- Trigger server-side event for same user
- View session in FullStory
- 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:
- Email: support@fullstory.com
- Include: API request/response logs, user IDs, session URLs
Next Steps:
- Event Tracking - Implement custom events
- Install or Embed Tag - Deploy FullStory client-side
- Data Layer Setup - Configure GTM data layer
Additional Resources: