> ## Documentation Index
> Fetch the complete documentation index at: https://docs.scanova.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Send Server-Side Events

> Send purchase, sign-up, and lead conversion events from your backend to Scanova using the server events API. Code examples in cURL, Node.js, Python, and PHP.

Use the server events API to report conversions that happen on your server — purchases, confirmed sign-ups, leads, or any backend action you want to attribute to a QR Code scan.

## Endpoint

```
POST https://track.scanova.io/server-events
```

**Required headers:**

```http theme={null}
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

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    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" }
      }'
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    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)}`);
      }
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    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()
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    <?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}");
        }
    }
    ```
  </Tab>
</Tabs>

## 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

The `scan_session_id` originates in the browser. You need to pass it from your frontend to your backend.

**Option 1: Hidden form field**

```html theme={null}
<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>
```

**Option 2: Include in API request body from frontend**

```javascript theme={null}
// _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
```

```json theme={null}
{
  "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-..."
    }
  ]
}
```

The response includes a per-event accepted/rejected breakdown:

```json theme={null}
{
  "accepted": 2,
  "rejected": 0,
  "results": [
    { "index": 0, "event_id": "...", "status": "accepted" },
    { "index": 1, "event_id": "...", "status": "accepted" }
  ]
}
```

## Next steps

* [Idempotency & Retries](/conversion-tracking/server/idempotency-retries) — how to safely retry failed requests
* [Verify Delivery](/conversion-tracking/server/verify) — confirm events are received
* [Single Event API Reference](/conversion-tracking/api/events-collect) — full endpoint specification
* [Batch Event API Reference](/conversion-tracking/api/events-batch) — batch endpoint specification
