Envoyez les événements d'analyse directement depuis votre backend vers Zenovay. Le suivi côté serveur est immunisé contre les bloqueurs de publicités, vous permet d'enregistrer les événements qui se produisent uniquement côté serveur (achats confirmés, abonnements pilotés par webhook), et vous donne un contrôle total sur ce qui est envoyé.
Pourquoi le suivi côté serveur ?
Avantages
| Avantage | Description |
|---|---|
| Immunité contre les bloqueurs de publicités | Les événements atteignent Zenovay même lorsque le script client est bloqué par le navigateur |
| Événements côté serveur uniquement | Suivez les choses qui se produisent côté serveur — paiements confirmés, tâches cron, webhooks |
| Contrôle des données | Vous décidez exactement ce qui est envoyé |
| Suivi hybride | Combinez avec le script client pour une couverture complète |
Quand utiliser
- Confirmation d'achat e-commerce (après confirmation du prestataire de paiement)
- Événements backend (abonnement créé, abonnement annulé)
- Événements pilotés par webhook (Stripe, prestataires de paiement)
- Suivi hybride (script client pour la navigation + serveur pour les conversions confirmées)
Endpoint de suivi
Les requêtes côté serveur utilisent le même endpoint d'ingestion que le script client. Envoyez un POST à l'endpoint de suivi avec le code de suivi de votre site dans le chemin :
POST https://api.zenovay.com/e/{trackingCode}
Il s'agit d'un endpoint public, non authentifié — il ne nécessite pas de clé API et il est ouvert sur tous les plans (c'est le même chemin vers lequel le script de suivi envoie). Le code de suivi est la même valeur que vous mettez dans l'attribut data-tracking-code de votre snippet d'installation.
Info
L'endpoint d'ingestion est partagé avec le suivi du navigateur, donc sa charge utile est structurée comme les données qu'un navigateur enverrait. Cela signifie que quelques champs que le navigateur remplit automatiquement — session_id, device_type, browser, os et user_agent — doivent être fournis par vous dans le corps JSON lors de l'appel depuis un serveur. Les requêtes auxquelles il manque ces éléments sont rejetées.
Champs requis
Chaque événement que vous envoyez doit inclure ces champs dans le corps JSON :
| Champ | Remarques |
|---|---|
session_id | Une chaîne de au moins 8 caractères. Réutilisez la même valeur pour les événements appartenant à la même visite. |
url | Une URL complète et valide (par exemple https://example.com/checkout/success). |
device_type | par exemple desktop, mobile, tablet ou server. |
browser | Un nom de navigateur/client. Utilisez quelque chose de descriptif pour le trafic serveur (par exemple server). |
os | Un nom de système d'exploitation (par exemple Linux). |
user_agent | Une chaîne user-agent. Évitez les agents génériques ressemblant à des bots (curl, python, wget, etc.) — ils sont filtrés comme bots. |
visitor_id est optionnel mais recommandé (≥8 caractères) pour que les événements répétés se lient au même visiteur.
Page vue vs. Événement personnalisé
Le type d'événement est défini avec le champ event_type :
- Page vue — omettez
event_type(par défaut) ou définissez-le surpageview. - Événement personnalisé — définissez
event_typesurcustom, mettez le nom de l'événement dansevent_nameet toutes les métadonnées dansproperties.
# Événement personnalisé (par exemple un achat confirmé)
curl -X POST "https://api.zenovay.com/e/YOUR_TRACKING_CODE" \
-H "Content-Type: application/json" \
-d '{
"event_type": "custom",
"event_name": "purchase",
"url": "https://example.com/checkout/success",
"referrer": "https://example.com/cart",
"session_id": "srv-9f2a7c41bd",
"device_type": "server",
"browser": "server",
"os": "Linux",
"user_agent": "MyApp-Server/1.0",
"properties": {
"order_id": "ORD-12345",
"value": 99.99,
"currency": "USD"
}
}'
# Page vue
curl -X POST "https://api.zenovay.com/e/YOUR_TRACKING_CODE" \
-H "Content-Type: application/json" \
-d '{
"event_type": "pageview",
"url": "https://example.com/products/widget",
"referrer": "https://google.com/search",
"session_id": "srv-9f2a7c41bd",
"device_type": "server",
"browser": "server",
"os": "Linux",
"user_agent": "MyApp-Server/1.0"
}'
Remarque sur la géolocalisation
Pour le trafic du navigateur, Zenovay dérive l'emplacement de l'IP du visiteur qui se connecte. Lorsque vous appelez l'endpoint depuis votre serveur, l'IP de connexion est celle de votre serveur — donc les événements seront géolocalisés vers votre infrastructure, pas l'utilisateur final.
Si vous avez besoin d'une géolocalisation précise par visiteur, effectuez ce suivi côté client (le snippet d'installation standard) ou utilisez le proxy first-party, et réservez le suivi côté serveur pour les événements backend (achats, abonnements) où l'emplacement exact de l'utilisateur n'est pas l'objectif. L'ajout d'un header X-Forwarded-For sur un simple appel serveur ne remplace pas l'IP du serveur pour l'endpoint public.
Exemples de mise en œuvre
Node.js / Express
// lib/analytics.js
const TRACKING_CODE = process.env.ZENOVAY_TRACKING_CODE;
const ENDPOINT = `https://api.zenovay.com/e/${TRACKING_CODE}`;
// Champs de base que chaque événement côté serveur a besoin.
const SERVER_DEFAULTS = {
device_type: 'server',
browser: 'server',
os: 'Linux',
user_agent: 'MyApp-Server/1.0'
};
async function trackEvent(eventName, { url, sessionId, properties = {} }) {
const payload = {
event_type: 'custom',
event_name: eventName,
url,
session_id: sessionId,
properties,
...SERVER_DEFAULTS
};
try {
const response = await fetch(ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
return response.ok;
} catch (error) {
console.error('Analytics error:', error);
return false;
}
}
async function trackPageview({ url, sessionId, referrer = '' }) {
const payload = {
event_type: 'pageview',
url,
referrer,
session_id: sessionId,
...SERVER_DEFAULTS
};
try {
const response = await fetch(ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
return response.ok;
} catch (error) {
console.error('Pageview tracking error:', error);
return false;
}
}
module.exports = { trackEvent, trackPageview };
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}'
SERVER_DEFAULTS = {
'device_type': 'server',
'browser': 'server',
'os': 'Linux',
'user_agent': 'MyApp-Server/1.0',
}
def track_event(event_name, session_id, url, properties=None):
payload = {
'event_type': 'custom',
'event_name': event_name,
'url': url,
'session_id': session_id,
'properties': properties or {},
**SERVER_DEFAULTS,
}
try:
response = requests.post(TRACKING_URL, json=payload, timeout=5)
return response.ok
except Exception as e:
print(f'Analytics error: {e}')
return False
def track_pageview(session_id, url, referrer=''):
payload = {
'event_type': 'pageview',
'url': url,
'referrer': referrer,
'session_id': session_id,
**SERVER_DEFAULTS,
}
try:
response = requests.post(TRACKING_URL, json=payload, timeout=5)
return response.ok
except Exception as e:
print(f'Pageview tracking error: {e}')
return False
PHP
<?php
class ZenovayAnalytics {
private $trackingUrl;
private $defaults = [
'device_type' => 'server',
'browser' => 'server',
'os' => 'Linux',
'user_agent' => 'MyApp-Server/1.0',
];
public function __construct($trackingCode) {
$this->trackingUrl = "https://api.zenovay.com/e/{$trackingCode}";
}
public function trackEvent($eventName, $sessionId, $url, $properties = []) {
$payload = array_merge([
'event_type' => 'custom',
'event_name' => $eventName,
'url' => $url,
'session_id' => $sessionId,
'properties' => $properties,
], $this->defaults);
return $this->sendRequest($payload);
}
public function trackPageview($sessionId, $url, $referrer = '') {
$payload = array_merge([
'event_type' => 'pageview',
'url' => $url,
'referrer' => $referrer,
'session_id' => $sessionId,
], $this->defaults);
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'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5
]);
curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $httpCode >= 200 && $httpCode < 300;
}
}
// Utilisation
$analytics = new ZenovayAnalytics(getenv('ZENOVAY_TRACKING_CODE'));
$analytics->trackEvent('purchase', 'srv-9f2a7c41bd', 'https://example.com/checkout/success', [
'order_id' => 'ORD-12345',
'value' => 99.99
]);
Suivi hybride
Le modèle le plus courant : suivez la navigation avec le script client et confirmez les conversions depuis le serveur une fois que le prestataire de paiement les a confirmées.
// Côté client : suivez l'intention avec le script Zenovay
window.zenovay('track', 'checkout_started');
// Côté serveur : suivez l'événement confirmé depuis votre gestionnaire webhook
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',
sessionId: 'srv-' + event.data.object.id.slice(-12),
properties: {
order_id: event.data.object.id,
value: event.data.object.amount_total / 100
}
});
}
res.sendStatus(200);
});
Filtrage des bots
Zenovay filtre déjà le trafic bot évidemment sur le côté ingestion, donc les événements envoyés avec des agents génériques comme curl, wget, python ou tout ce qui correspond aux modèles de crawler courants sont rejetés. C'est pourquoi les exemples ci-dessus utilisent un user_agent descriptif pour votre serveur.
Si vous transmettez également de vraies requêtes client via votre backend, filtrez les crawlers avant d'envoyer pour ne pas brûler votre limite de débit sur le trafic qui serait rejeté de toute façon :
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 || ''));
}
Limites de débit
L'endpoint de suivi a des limites de débit par adresse IP :
- Limite de rafale : 60 requêtes par 10 secondes
- Limite durable : 5 000 requêtes par heure
Ces limites s'appliquent spécifiquement à l'endpoint d'ingestion de suivi, pas à l'API REST (qui est une fonctionnalité payante séparée avec ses propres limites).
Dépannage
Les événements n'apparaissent pas
Vérifiez :
- Le code de suivi dans le chemin URL est correct.
- Tous les champs requis sont présents (
session_idde 8+ caractères,urlvalide,device_type,browser,os,user_agent). Les champs manquants retournent un400avec une liste de ce qui manque. - Votre
user_agentn'est pas filtré comme un bot (évitezcurl,python, etc.). - L'IP n'est pas limitée en débit.
Événements en double
Assurez-vous :
- Vous ne suivez pas le même événement à la fois côté client et côté serveur.
- Les gestionnaires webhook ne déclenchent pas plus d'une fois pour le même événement.
La géolocalisation reflète votre serveur
C'est normal pour les appels côté serveur — l'IP de connexion est votre serveur, pas le visiteur. Utilisez le script client (ou un proxy first-party) pour une géolocalisation précise du visiteur et utilisez le suivi côté serveur pour les événements backend où l'emplacement n'est pas l'objectif.