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

# Idempotence et déduplication

> LigdiCash envoie deux requêtes POST par événement. Comment garantir que votre traitement n'est exécuté qu'une seule fois.

Pour chaque événement de transaction, LigdiCash envoie **deux requêtes POST** à votre `callback_url` :

* Une en `application/x-www-form-urlencoded`
* Une en `application/json`

Les deux contiennent exactement les mêmes données. Votre serveur va donc recevoir deux appels pour un seul événement. Sans déduplication, vous risquez de traiter la même transaction deux fois — livrer une commande en double, déclencher deux remboursements, créditer un compte deux fois.

## Stratégie de déduplication

La méthode la plus simple consiste à marquer chaque transaction traitée dans votre base de données et à ignorer les appels suivants pour la même transaction.

<Steps>
  <Step title="Extraire votre transaction_id">
    Parsez `custom_data` pour extraire votre `transaction_id` via `keyof_customdata`. Voir [Parser custom\_data](/api-paiement/callback/parser-custom-data).
  </Step>

  <Step title="Vérifier si déjà traité">
    Avant tout traitement, cherchez si cet identifiant existe déjà dans votre table de transactions traitées.
  </Step>

  <Step title="Traiter et marquer atomiquement">
    Si non traité, effectuez votre traitement et marquez la transaction comme traitée dans la même transaction de base de données. L'atomicité évite les race conditions si les deux requêtes arrivent simultanément.
  </Step>
</Steps>

## Implémentation

```javascript theme={null}
app.post('/callback', async (req, res) => {
  const entry = req.body.custom_data?.find(
    (item) => item.keyof_customdata === 'transaction_id'
  );
  const transactionId = entry?.valueof_customdata;
  if (!transactionId) return res.sendStatus(400);

  // Insertion avec contrainte d'unicité — échoue si déjà traité
  const inserted = await db.query(
    'INSERT INTO processed_callbacks (transaction_id) VALUES (?) ON CONFLICT DO NOTHING',
    [transactionId]
  );

  if (inserted.rowCount === 0) {
    // Déjà traité — deuxième requête du doublon LigdiCash
    return res.sendStatus(200);
  }

  // Première réception — traitement métier
  await processPayment(transactionId);

  res.sendStatus(200);
});
```

```php theme={null}
// Avec une contrainte UNIQUE sur transaction_id en base
try {
    $db->insert('processed_callbacks', ['transaction_id' => $transactionId]);
} catch (UniqueConstraintException $e) {
    // Déjà traité
    http_response_code(200);
    exit;
}

// Première réception — traitement métier
processPayment($transactionId);
```

<Note>
  La contrainte d'unicité en base de données est plus fiable qu'un simple `SELECT` suivi d'un `INSERT` — deux requêtes simultanées peuvent passer le `SELECT` en même temps avant que l'une des deux n'ait effectué son `INSERT`.
</Note>

## Pages associées

* [Introduction au callback](/api-paiement/callback/introduction)
* [Parser custom\_data](/api-paiement/callback/parser-custom-data)
* [Exemples par framework](/api-paiement/callback/exemples-frameworks)
