Server-side tracking
Send events directly from your backend — ad-blocker resilient, source of truth for revenue.
When to use server-side tracking
- Conversions where the truth lives in your DB (purchases, signups, plan upgrades).
- Webhooks from payment processors, CRMs, or email providers.
- Mobile backends until the native SDKs are released.
- Any event you want to capture even when the user has an ad-blocker.
There is currently no official PHP/Node/Python SDK package — server-side tracking uses the REST API directly via your language's standard HTTP client. The examples below are intentionally minimal and have no extra dependencies.
Endpoint and auth
- URL:
POST https://api.openanalyticsapi.com/v1/projects/{PROJECT_ID}/events - Auth:
Authorization: Bearer oa_live_xxxxxxxxxxxx - Content-Type:
application/json - Response:
202 Acceptedwith{"queued":true}on success
Security reminder. The oa_live_* key is a secret. Read it from an environment variable; never commit it to git, never inline it in a public repository, and never expose it to the browser.
Event payload
| Field | Required | Notes |
|---|---|---|
| event_type | yes | pageview, event, identify, conversion |
| event_name | conditional | Required for event and conversion. |
| user_id | recommended | Hashed / anonymized identifier. Never send raw emails. |
| url | recommended | The URL the event is associated with. |
| properties | no | Free-form JSON object with up to 50 keys. |
| revenue | no | Decimal, used for conversions. |
| currency | no | ISO 4217 code (e.g. USD, EUR). |
| timestamp | no | Unix ms. Defaults to receive time. |
PHP example
Plain PHP 8 using the built-in cURL extension. No framework, no Composer dependency.
<?php
function oaa_track(string $eventType, string $eventName, array $extra = []): bool {
$projectId = getenv('OAA_PROJECT_ID');
$apiKey = getenv('OAA_API_KEY'); // oa_live_xxx
if ($projectId === false || $apiKey === false) {
return false;
}
$payload = array_merge([
'event_type' => $eventType,
'event_name' => $eventName,
'timestamp' => (int) (microtime(true) * 1000),
], $extra);
$url = "https://api.openanalyticsapi.com/v1/projects/{$projectId}/events";
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$apiKey}",
'Content-Type: application/json',
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $status === 202;
}
// Conversion from a webhook handler
oaa_track('conversion', 'purchase', [
'user_id' => hash('sha256', $userEmail),
'url' => 'https://example.com/thanks',
'revenue' => 49.99,
'currency' => 'USD',
'properties' => ['plan' => 'pro'],
]);
For high-volume backends, batch with POST /v1/projects/{id}/events/batch (up to 1000 events per call).
Node.js example
Node 18+ with the built-in fetch. No npm dependency.
const OAA_PROJECT_ID = process.env.OAA_PROJECT_ID;
const OAA_API_KEY = process.env.OAA_API_KEY; // oa_live_xxx
async function oaaTrack(eventType, eventName, extra = {}) {
const url = `https://api.openanalyticsapi.com/v1/projects/${OAA_PROJECT_ID}/events`;
const payload = {
event_type: eventType,
event_name: eventName,
timestamp: Date.now(),
...extra,
};
const res = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Bearer ${OAA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
return res.status === 202;
}
// Conversion from a Stripe webhook
await oaaTrack('conversion', 'purchase', {
user_id: crypto.createHash('sha256').update(userEmail).digest('hex'),
url: 'https://example.com/thanks',
revenue: 49.99,
currency: 'USD',
properties: { plan: 'pro' },
});
Retries & duplicate handling
The current ingest pipeline does not deduplicate by an idempotency key. If your webhook handler retries the same event, you'll get the event twice in your analytics. Two practical patterns:
- Use a stable, deterministic
event_name+propertiesthat include a unique business ID (e.g.order_id) — then dedupe in your downstream queries. - Track the last-sent event ID in your DB and skip the call if it was already sent.
Server-side Idempotency-Key header support is on the roadmap.