Track analytics events from your server for enhanced accuracy, ad-blocker immunity, and complete control over data collection.
Why Server-Side Tracking?
Benefits
| Benefit | Description |
|---|---|
| Ad-blocker immunity | Events tracked regardless of client blockers |
| Enhanced accuracy | No JavaScript required, works everywhere |
| Bot filtering | Filter bots before tracking |
| Data control | Full control over what data is sent |
| Privacy compliance | Easier GDPR/CCPA compliance |
| Sensitive events | Track backend events (purchases, signups) |
When to Use
- E-commerce purchase tracking
- Form submissions
- Backend events (subscriptions, cancellations)
- Hybrid tracking (client + server)
- Privacy-sensitive environments
Tracking Endpoint
Server-side tracking uses the same tracking endpoint as the client-side script. Send a POST request to the tracking endpoint with your website's tracking code:
POST https://api.zenovay.com/e/{trackingCode}
This is a public endpoint and does not require an API key. The tracking code is the same code used in the data-tracking-code attribute of your tracking script.
Request Format
curl -X POST "https://api.zenovay.com/e/YOUR_TRACKING_CODE" \
-H "Content-Type: application/json" \
-d '{
"type": "event",
"url": "https://example.com/checkout/success",
"referrer": "https://example.com/cart",
"name": "purchase",
"data": {
"order_id": "ORD-12345",
"value": 99.99,
"currency": "USD"
}
}'
Passing Visitor Information
For accurate geolocation and device detection, forward the original visitor's headers:
curl -X POST "https://api.zenovay.com/e/YOUR_TRACKING_CODE" \
-H "Content-Type: application/json" \
-H "User-Agent: Mozilla/5.0..." \
-H "X-Forwarded-For: 203.0.113.50" \
-d '{
"type": "pageview",
"url": "https://example.com/products/widget",
"referrer": "https://google.com/search"
}'
Implementation Examples
Node.js / Express
// lib/analytics.js
const TRACKING_CODE = process.env.ZENOVAY_TRACKING_CODE;
async function trackEvent(name, data, req = null) {
const payload = {
type: 'event',
url: data.url || req?.headers?.referer,
name: name,
data: data.properties || {}
};
const headers = {
'Content-Type': 'application/json'
};
// Forward visitor headers for geolocation
if (req) {
headers['User-Agent'] = req.headers['user-agent'];
headers['X-Forwarded-For'] = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
}
try {
const response = await fetch(
`https://api.zenovay.com/e/${TRACKING_CODE}`,
{
method: 'POST',
headers,
body: JSON.stringify(payload)
}
);
return response.ok;
} catch (error) {
console.error('Analytics error:', error);
return false;
}
}
async function trackPageview(url, req) {
const payload = {
type: 'pageview',
url
};
const headers = {
'Content-Type': 'application/json'
};
if (req) {
headers['User-Agent'] = req.headers['user-agent'];
headers['X-Forwarded-For'] = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
}
try {
const response = await fetch(
`https://api.zenovay.com/e/${TRACKING_CODE}`,
{
method: 'POST',
headers,
body: JSON.stringify(payload)
}
);
return response.ok;
} catch (error) {
console.error('Pageview tracking error:', error);
return false;
}
}
module.exports = { trackEvent, trackPageview };
Express.js Middleware
// middleware/analytics.js
const { trackPageview } = require('../lib/analytics');
function analyticsMiddleware(options = {}) {
return async (req, res, next) => {
// Skip static files and API routes
if (req.path.match(/\.(js|css|png|jpg|ico)$/) || req.path.startsWith('/api/')) {
return next();
}
// Track pageview asynchronously (don't block response)
trackPageview(req.originalUrl, req).catch(console.error);
next();
};
}
module.exports = analyticsMiddleware;
// app.js
const analyticsMiddleware = require('./middleware/analytics');
app.use(analyticsMiddleware());
Python (Flask)
# analytics.py
import os
import requests
from flask import request
TRACKING_CODE = os.getenv('ZENOVAY_TRACKING_CODE')
TRACKING_URL = f'https://api.zenovay.com/e/{TRACKING_CODE}'
def track_event(event_name, properties=None, url=None):
payload = {
'type': 'event',
'url': url or request.url,
'name': event_name,
'data': properties or {}
}
headers = {
'Content-Type': 'application/json',
'User-Agent': request.headers.get('User-Agent', ''),
'X-Forwarded-For': request.headers.get('X-Forwarded-For', request.remote_addr)
}
try:
response = requests.post(TRACKING_URL, json=payload, headers=headers, timeout=5)
return response.ok
except Exception as e:
print(f'Analytics error: {e}')
return False
def track_pageview(url=None):
payload = {
'type': 'pageview',
'url': url or request.url,
'referrer': request.headers.get('Referer', '')
}
headers = {
'Content-Type': 'application/json',
'User-Agent': request.headers.get('User-Agent', ''),
'X-Forwarded-For': request.headers.get('X-Forwarded-For', request.remote_addr)
}
try:
response = requests.post(TRACKING_URL, json=payload, headers=headers, timeout=5)
return response.ok
except Exception as e:
print(f'Pageview tracking error: {e}')
return False
PHP
<?php
class ZenovayAnalytics {
private $trackingCode;
private $trackingUrl;
public function __construct($trackingCode) {
$this->trackingCode = $trackingCode;
$this->trackingUrl = "https://api.zenovay.com/e/{$trackingCode}";
}
public function trackEvent($eventName, $properties = [], $url = null) {
$payload = [
'type' => 'event',
'url' => $url ?? 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
'name' => $eventName,
'data' => $properties
];
return $this->sendRequest($payload);
}
public function trackPageview($url = null) {
$payload = [
'type' => 'pageview',
'url' => $url ?? 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
'referrer' => $_SERVER['HTTP_REFERER'] ?? ''
];
return $this->sendRequest($payload);
}
private function sendRequest($payload) {
$ch = curl_init($this->trackingUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'User-Agent: ' . ($_SERVER['HTTP_USER_AGENT'] ?? ''),
'X-Forwarded-For: ' . ($_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '')
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5
]);
curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $httpCode >= 200 && $httpCode < 300;
}
}
// Usage
$analytics = new ZenovayAnalytics(getenv('ZENOVAY_TRACKING_CODE'));
$analytics->trackEvent('purchase', ['order_id' => 'ORD-12345', 'value' => 99.99]);
Hybrid Tracking
Combine client and server-side tracking for the best of both worlds:
// Client-side: Track interactive events using the Zenovay script
window.zenovay('track', 'checkout_started');
// Server-side: Track confirmed backend events
app.post('/webhooks/stripe', async (req, res) => {
const event = req.body;
if (event.type === 'checkout.session.completed') {
await trackEvent('purchase', {
url: 'https://example.com/checkout/success',
properties: {
order_id: event.data.object.id,
value: event.data.object.amount_total / 100
}
});
}
res.sendStatus(200);
});
Bot Filtering
Filter bots before tracking:
function isBot(userAgent) {
const botPatterns = [
/bot/i, /crawler/i, /spider/i, /scraper/i,
/curl/i, /wget/i, /python/i, /java\//i,
/googlebot/i, /bingbot/i, /yandex/i
];
return botPatterns.some(pattern => pattern.test(userAgent));
}
app.use((req, res, next) => {
if (!isBot(req.headers['user-agent'])) {
trackPageview(req.originalUrl, req);
}
next();
});
Rate Limits
The tracking endpoint has rate limits applied per IP address:
- Burst limit: 60 requests per 10 seconds
- Sustained limit: 5,000 requests per hour
These limits apply to the tracking endpoint specifically, not the External API.
Troubleshooting
Events Not Appearing
Check:
- Tracking code is correct
- Request format matches specification
- IP not blocked or rate limited
- Correct Content-Type header
Duplicate Events
Ensure:
- Not tracking both client and server for the same event
- Check for middleware running multiple times
Missing Geolocation
Verify:
- X-Forwarded-For header is being passed with the visitor's real IP
- Not using localhost/private IPs in production