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

# Enviar eventos de servidor

> Envía conversiones de compra, registro y lead desde tu backend a la API de seguimiento de Scanova, con ejemplos de código en cURL, Node.js, Python y PHP.

Usa la API de eventos de servidor para informar de las conversiones que ocurren en tu servidor: compras, registros confirmados, leads o cualquier acción del backend que quieras atribuir a un escaneo de código QR.

## Endpoint

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

**Cabeceras obligatorias:**

```http theme={null}
Content-Type: application/json
X-API-Key: YOUR_SITE_API_KEY
```

## Campos obligatorios

| Campo             | Descripción                                                                                                         |
| ----------------- | ------------------------------------------------------------------------------------------------------------------- |
| `site_id`         | El ID de tu sitio de seguimiento, disponible en el panel                                                            |
| `event_name`      | El nombre del evento de conversión (por ejemplo `purchase`, `signup` o `lead`)                                      |
| `scan_session_id` | El ID de sesión de escaneo del navegador del usuario. Es lo que vincula la conversión con el escaneo del código QR. |

## Ejemplos

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

## Campos opcionales

| Campo              | Descripción                                                                                                                             |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| `event_id`         | UUID para reintentos sin riesgo. Genéralo una vez y reutilízalo en cada reintento. Si lo omites, se genera automáticamente.             |
| `event_time`       | Marca de tiempo ISO 8601 de cuándo ocurrió el evento. Por defecto, la hora de recepción. Útil si envías los eventos de forma asíncrona. |
| `conversion_value` | `{ "amount": 49.99, "currency": "USD" }`. La moneda debe ser un código ISO 4217 de tres letras.                                         |
| `user_identifiers` | Identificadores de usuario con hash: `email_hash`, `phone_hash`, `external_id`. Nunca envíes el correo ni el teléfono en texto plano.   |
| `properties`       | Objeto propio de clave-valor. Máximo 10 KB. Sin datos personales en texto plano.                                                        |
| `consent`          | `granted`, `denied` o `pending`. Controla el tratamiento de los datos personales.                                                       |

## Pasar scan\_session\_id del navegador al servidor

El `scan_session_id` nace en el navegador. Tienes que pasarlo de tu frontend a tu backend.

**Opción 1: campo de formulario oculto**

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

**Opción 2: incluirlo desde el frontend en el cuerpo de la petición**

```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
  }),
});
```

## Enviar un lote de eventos

Usa el endpoint de lotes para enviar hasta 100 eventos en una sola petición:

```
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-..."
    }
  ]
}
```

La respuesta detalla, evento a evento, cuáles se aceptaron y cuáles se rechazaron:

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

## Siguientes pasos

* [Idempotencia y reintentos](/es/conversion-tracking/server/idempotency-retries) — cómo reintentar peticiones fallidas sin riesgo
* [Verificar la entrega](/es/conversion-tracking/server/verify) — comprueba que los eventos llegan
* [Referencia de API: evento único](/es/conversion-tracking/api/events-collect) — especificación completa del endpoint
* [Referencia de API: lotes de eventos](/es/conversion-tracking/api/events-batch) — especificación del endpoint de lotes
