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

# Sécuriser le callback

> Pourquoi ne jamais faire confiance au payload reçu, et comment re-vérifier chaque callback avec le token stocké à la création.

Votre URL de callback est une route POST publique. N'importe qui connaissant cette URL peut envoyer un faux payload — avec un `status: "completed"` et un montant de son choix. Si vous traitez ce payload sans vérification, vous livrez une commande ou déclenchez un payout sur la base d'un paiement qui n'a jamais eu lieu.

**La règle d'or : ne jamais agir sur le payload reçu. Toujours re-vérifier.**

## Le pattern de re-vérification

<Steps>
  <Step title="À la création — stocker le token">
    Lors de la création d'une transaction (payin ou payout), stockez le `token` retourné par l'API dans votre base de données, associé à votre `transaction_id`.

    ```json theme={null}
    // Réponse de création — à stocker
    {
      "response_code": "00",
      "token": "{INVOICE_TOKEN}"
    }
    ```
  </Step>

  <Step title="À la réception du callback — extraire l'identifiant">
    Recevez le callback. Extrayez votre `transaction_id` depuis le tableau `custom_data` en filtrant sur `keyof_customdata`.

    ```javascript theme={null}
    const entry = payload.custom_data?.find(
      (item) => item.keyof_customdata === "transaction_id"
    );
    const transactionId = entry?.valueof_customdata;
    ```
  </Step>

  <Step title="Retrouver le token stocké">
    Cherchez dans votre base de données le token associé à ce `transaction_id`. Si aucun enregistrement ne correspond, ignorez le callback — il est probablement frauduleux.

    ```javascript theme={null}
    const stored = await db.transactions.findOne({ transaction_id: transactionId });
    if (!stored) return; // callback non sollicité
    ```
  </Step>

  <Step title="Appeler l'endpoint confirm avec le token stocké">
    Appelez l'endpoint de vérification de LigdiCash avec le token que **vous** avez stocké — pas celui du callback (qui est toujours vide).

    ```javascript theme={null}
    // Pour un payin redirect
    const params = new URLSearchParams({ invoiceToken: stored.token });
    const verify = await fetch(
      `https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/confirm?${params}`,
      { headers: { Apikey: API_KEY, Authorization: `Bearer ${AUTH_TOKEN}` } }
    );
    const result = await verify.json();
    ```
  </Step>

  <Step title="Agir sur le résultat de confirm, pas sur le payload">
    Utilisez uniquement le `status` retourné par `confirm` pour décider de votre action.

    ```javascript theme={null}
    if (result.status === 'completed') {
      await db.orders.markAsPaid(transactionId);
    }
    ```
  </Step>
</Steps>

```mermaid theme={null}
flowchart TD
    A["Callback reçu — POST /callback_url"] --> B["Extraire transaction_id\ndepuis custom_data"]
    B --> C{transaction_id\ntrouvé en DB ?}
    C -->|Non| D["Ignorer\ncallback non sollicité"]
    C -->|Oui| E{Statut en DB ?}
    E -->|"completed / notcompleted"| F["Ignorer\ndoublon déjà traité"]
    E -->|pending| G["GET /confirm\navec le token stocké à la création"]
    G --> H{Statut retourné\npar confirm ?}
    H -->|completed| I["Confirmer la commande\nMettre à jour la DB"]
    H -->|notcompleted| J["Annuler la commande\nMettre à jour la DB"]
    H -->|pending| K["Ne rien faire\nAttendre le prochain callback"]
```

## Ce qu'il ne faut pas faire

```javascript theme={null}
// DANGEREUX — traitement direct du payload sans vérification
app.post('/callback', (req, res) => {
  const { status, amount, external_id } = req.body;

  if (status === 'completed') {
    // N'importe qui peut envoyer ce payload
    markOrderAsPaid(external_id, amount);
  }
});
```

```javascript theme={null}
// CORRECT — re-vérification systématique
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);

  const stored = await db.transactions.findOne({ transaction_id: transactionId });
  if (!stored) return res.sendStatus(404);

  const result = await confirmWithLigdicash(stored.token);

  if (result.status === 'completed') {
    await markOrderAsPaid(transactionId);
  }

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

## Pages associées

* [Payload Payin](/api-paiement/callback/payload-payin)
* [Parser custom\_data](/api-paiement/callback/parser-custom-data)
* [Exemples par framework](/api-paiement/callback/exemples-frameworks)
