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

# Envoyer des événements serveur

> Envoyez vos conversions d'achat, d'inscription et de lead depuis votre backend vers l'API de suivi Scanova, avec des exemples en cURL, Node.js, Python et PHP.

Utilisez l'API des événements serveur pour déclarer les conversions qui se produisent sur votre serveur : achats, inscriptions confirmées, leads ou toute action du backend que vous voulez attribuer à un scan de QR Code.

## Point de terminaison

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

**En-têtes obligatoires :**

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

## Champs obligatoires

| Champ             | Description                                                                                                            |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `site_id`         | Identifiant de votre site de suivi, disponible dans le tableau de bord                                                 |
| `event_name`      | Le nom de l'événement de conversion (par exemple `purchase`, `signup` ou `lead`)                                       |
| `scan_session_id` | L'identifiant de session de scan issu du navigateur du visiteur. C'est lui qui relie la conversion au scan du QR Code. |

## Exemples

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

## Champs facultatifs

| Champ              | Description                                                                                                                                             |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event_id`         | UUID pour relancer sans risque. Générez-le une fois et réutilisez-le à chaque relance. Sans valeur, il est généré automatiquement.                      |
| `event_time`       | Horodatage ISO 8601 du moment où l'événement s'est produit. Par défaut, l'heure de réception. Utile si vous envoyez les événements de façon asynchrone. |
| `conversion_value` | `{ "amount": 49.99, "currency": "USD" }`. La devise doit être un code ISO 4217 à trois lettres.                                                         |
| `user_identifiers` | Identifiants hachés du visiteur : `email_hash`, `phone_hash`, `external_id`. N'envoyez jamais d'e-mail ni de téléphone en clair.                        |
| `properties`       | Objet clé-valeur libre. 10 Ko au maximum. Aucune donnée personnelle en clair.                                                                           |
| `consent`          | `granted`, `denied` ou `pending`. Détermine le traitement des données personnelles.                                                                     |

## Transmettre scan\_session\_id du navigateur au serveur

Le `scan_session_id` naît dans le navigateur. Il vous revient de le transmettre de votre frontend à votre backend.

**Option 1 : champ de formulaire masqué**

```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 : dans le corps de la requête API, depuis le 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
  }),
});
```

## Envoyer un lot d'événements

Le point de terminaison de lot permet d'envoyer jusqu'à 100 événements en une seule requête :

```
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 réponse indique, événement par événement, ce qui a été accepté ou rejeté :

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

## Étapes suivantes

* [Idempotence et relances](/fr/conversion-tracking/server/idempotency-retries) — relancer une requête en échec sans risque
* [Vérifier la réception](/fr/conversion-tracking/server/verify) — confirmer que les événements sont bien reçus
* [Référence API : événement unique](/fr/conversion-tracking/api/events-collect) — spécification complète du point de terminaison
* [Référence API : lot d'événements](/fr/conversion-tracking/api/events-batch) — spécification du point de terminaison de lot
