Endpoint
POST https://track.scanova.io/server-events
Content-Type: application/json
X-API-Key: YOUR_SITE_API_KEY
Required fields
| Field | Description |
|---|---|
site_id | Your tracking site ID from the dashboard |
event_name | The conversion event name (e.g. purchase, signup, lead) |
scan_session_id | The scan session ID from the user’s browser. This links the conversion to the QR scan. |
Examples
- cURL
- Node.js
- Python
- PHP
curl -X POST "https://track.scanova.io/server-events" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{
"site_id": "YOUR_SITE_ID",
"event_name": "purchase",
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"scan_session_id": "7ad26d4f-3181-4ef8-b6ca-b8f59499dd43",
"conversion_value": { "amount": 49.99, "currency": "USD" },
"properties": { "order_id": "ord_9876", "plan": "pro" }
}'
import crypto from 'node:crypto';
async function trackConversion({ scanSessionId, orderId, amount }) {
const response = await fetch('https://track.scanova.io/server-events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.SCANOVA_API_KEY,
},
body: JSON.stringify({
site_id: process.env.SCANOVA_SITE_ID,
event_name: 'purchase',
event_id: crypto.randomUUID(), // generate once and persist for retries
scan_session_id: scanSessionId,
conversion_value: { amount, currency: 'USD' },
properties: { order_id: orderId },
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Tracking failed: ${response.status} — ${JSON.stringify(error)}`);
}
}
import os
import uuid
import requests
def track_conversion(scan_session_id: str, order_id: str, amount: float):
response = requests.post(
"https://track.scanova.io/server-events",
headers={
"Content-Type": "application/json",
"X-API-Key": os.environ["SCANOVA_API_KEY"],
},
json={
"site_id": os.environ["SCANOVA_SITE_ID"],
"event_name": "purchase",
"event_id": str(uuid.uuid4()), # generate once, persist for retries
"scan_session_id": scan_session_id,
"conversion_value": {"amount": amount, "currency": "USD"},
"properties": {"order_id": order_id},
},
timeout=10,
)
response.raise_for_status()
<?php
function trackConversion(string $scanSessionId, string $orderId, float $amount): void {
$payload = json_encode([
'site_id' => getenv('SCANOVA_SITE_ID'),
'event_name' => 'purchase',
'event_id' => sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)),
'scan_session_id' => $scanSessionId,
'conversion_value' => ['amount' => $amount, 'currency' => 'USD'],
'properties' => ['order_id' => $orderId],
]);
$ch = curl_init('https://track.scanova.io/server-events');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: ' . getenv('SCANOVA_API_KEY'),
],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new RuntimeException("Tracking failed: {$status} — {$response}");
}
}
Optional fields
| Field | Description |
|---|---|
event_id | UUID for safe retries. Generate once and reuse on retry. If omitted, one is auto-generated. |
event_time | ISO 8601 timestamp of when the event occurred. Defaults to time of receipt. Useful if you are sending events asynchronously. |
conversion_value | { "amount": 49.99, "currency": "USD" }. Currency must be a 3-letter ISO 4217 code. |
user_identifiers | Hashed user identifiers: email_hash, phone_hash, external_id. Never send raw email or phone. |
properties | Custom key-value object. Max 10 KB. No raw PII. |
consent | granted, denied, or pending. Controls PII handling. |
Passing scan_session_id from browser to server
Thescan_session_id originates in the browser. You need to pass it from your frontend to your backend.
Option 1: Hidden form field
<form action="/checkout" method="POST">
<input type="hidden" name="scan_session_id" id="scan_session_id_field">
<!-- other form fields -->
</form>
<script>
// _scnv is the SDK's internal localStorage key (structure may change in future SDK versions)
const stored = localStorage.getItem('_scnv');
const sessionId = stored ? JSON.parse(stored).sid : null;
if (sessionId) {
document.getElementById('scan_session_id_field').value = sessionId;
}
</script>
// _scnv is the SDK's internal localStorage key (structure may change in future SDK versions)
const stored = localStorage.getItem('_scnv');
const scanSessionId = stored ? JSON.parse(stored).sid : null;
await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
cart: cartData,
scan_session_id: scanSessionId, // pass to your server
}),
});
Sending a batch of events
Use the batch endpoint to send up to 100 events in a single request:POST https://track.scanova.io/server-events/batch
{
"events": [
{
"site_id": "YOUR_SITE_ID",
"event_name": "purchase",
"scan_session_id": "7ad26d4f-...",
"conversion_value": { "amount": 49.99, "currency": "USD" }
},
{
"site_id": "YOUR_SITE_ID",
"event_name": "signup",
"scan_session_id": "3b5e1234-..."
}
]
}
{
"accepted": 2,
"rejected": 0,
"results": [
{ "index": 0, "event_id": "...", "status": "accepted" },
{ "index": 1, "event_id": "...", "status": "accepted" }
]
}
Next steps
- Idempotency & Retries — how to safely retry failed requests
- Verify Delivery — confirm events are received
- Single Event API Reference — full endpoint specification
- Batch Event API Reference — batch endpoint specification