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

# Le pattern transaction_id : la règle d'or

> Pourquoi générer et stocker votre propre identifiant de transaction avant chaque appel API LigdiCash — et comment le retrouver dans le callback et le dashboard.

Lorsque vous créez une transaction via l'API LigdiCash, vous recevez un `token` en retour. Ce token **n'est pas stable** : sa valeur dans le callback diffère de celle obtenue à la création. Si vous vous appuyez sur lui pour identifier vos transactions, vous perdrez le fil entre ce que vous avez initié et ce que vous avez confirmé.

La solution est simple : générez votre propre identifiant côté marchand, stockez-le **avant** d'appeler l'API, et passez-le dans le champ `custom_data`. LigdiCash vous le renverra intact dans le callback, et l'affichera dans votre dashboard.

## Le problème : le token change entre création et callback

À la création d'une transaction, l'API retourne un `token` :

```json theme={null}
{
  "response_code": "00",
  "token": "abc123-creation",
  ...
}
```

Lorsque LigdiCash vous notifie du résultat via le callback, ce même champ contient une **valeur différente** :

```json theme={null}
{
  "token": "xyz789-callback",
  ...
}
```

Les deux tokens désignent la même transaction, mais vous ne pouvez pas les relier directement. Sans identifiant propre à votre système, la réconciliation est impossible.

## La solution : injecter votre transaction\_id dans custom\_data

Le champ `custom_data` de chaque requête est un **objet JSON** dont vous définissez librement les clés. C'est ici que vous injectez votre identifiant :

```json theme={null}
"custom_data": {
  "transaction_id": "COMMANDE-20240815-00042"
}
```

Dans le callback, LigdiCash transforme cet objet en un **tableau**. Votre `transaction_id` y apparaît sous cette forme :

```json theme={null}
"custom_data": [
  {
    "keyof_customdata": "transaction_id",
    "valueof_customdata": "COMMANDE-20240815-00042",
    "datecreation_customdata": "2026-02-17 06:56:46.55245"
  }
]
```

Filtrez sur `keyof_customdata` pour extraire votre valeur — ne vous fiez pas à la position dans le tableau, d'autres entrées peuvent être présentes.

### Format du transaction\_id

Vous êtes libre de choisir le format qui correspond à votre métier :

| Format                | Exemple                                | Usage typique                      |
| --------------------- | -------------------------------------- | ---------------------------------- |
| Référence commande    | `ORD-2026-00042`                       | E-commerce — affiché au client     |
| Code court lisible    | `PAY-8KXZ`                             | Support client, mémorisable        |
| UUID v4               | `f47ac10b-58cc-4372-a567-0e02b2c3d479` | Systèmes distribués, usage interne |
| Timestamp + aléatoire | `1723718400-a3f9`                      | File de jobs                       |
| ID interne            | `order_8821`                           | Application existante              |

<Tip>
  Si vous affichez le `transaction_id` à vos clients — par exemple sur le reçu ou dans l'interface de suivi — privilégiez un format court et lisible comme `ORD-2026-00042`. Cela facilite les échanges avec votre support : le client peut le dicter ou le recopier sans erreur.
</Tip>

<Tip>
  Si le `transaction_id` reste purement interne, un UUID v4 est un bon choix par défaut : garanti unique sans coordination entre serveurs.
</Tip>

<Warning>
  Évitez les identifiants purement séquentiels exposés côté client (ex : `order_1`, `order_2`) — ils révèlent votre volume de transactions. Préférez un préfixe + compteur non trivial, ou un identifiant aléatoire.
</Warning>

## Cycle de vie recommandé

<Steps>
  <Step title="Générez le transaction_id">
    Avant d'appeler l'API, générez votre identifiant unique côté serveur.
  </Step>

  <Step title="Persistez-le en base">
    Enregistrez-le en base de données avec le statut `pending` **avant** d'appeler l'API. Si l'appel échoue, vous avez quand même la trace.
  </Step>

  <Step title="Passez-le dans custom_data">
    Incluez-le dans le tableau `custom_data` de votre requête de création.
  </Step>

  <Step title="Stockez le token de création">
    Conservez également le `token` retourné par l'API — il sera nécessaire pour appeler l'endpoint `confirm` lors de la re-vérification du callback.
  </Step>

  <Step title="Retrouvez-le dans le callback">
    Quand LigdiCash vous notifie, extrayez votre `transaction_id` depuis `custom_data` et mettez à jour le statut en base.
  </Step>
</Steps>

## Retrouver le transaction\_id dans le callback

Le payload du callback contient un champ `custom_data`. Sa forme varie selon le flux (voir [Parser custom\_data](/api-paiement/callback/parser-custom-data)), mais pour un payin il s'agit d'un tableau :

<CodeGroup>
  ```javascript Node.js theme={null}
  function extractTransactionId(customData) {
    if (!Array.isArray(customData)) return null;
    const entry = customData.find(
      (item) => item.keyof_customdata === "transaction_id"
    );
    return entry?.valueof_customdata ?? null;
  }
  ```

  ```php PHP theme={null}
  function extractTransactionId(array $customData): ?string {
      foreach ($customData as $item) {
          if ($item['keyof_customdata'] === 'transaction_id') {
              return $item['valueof_customdata'];
          }
      }
      return null;
  }
  ```

  ```python Python theme={null}
  def extract_transaction_id(custom_data: list) -> str | None:
      for item in custom_data:
          if item.get("keyof_customdata") == "transaction_id":
              return item.get("valueof_customdata")
      return None
  ```
</CodeGroup>

<Warning>
  Le callback contient aussi un champ `transaction_id` **à la racine** du payload. LigdiCash le construit en concaténant les `valueof_customdata` de toutes les entrées dont la clé contient `id` (ex : `transaction_id`, `partner_id`, `event_id`…), séparés par `;`. Si vous avez injecté plusieurs champs de ce type, cette valeur racine sera un mélange — pas votre identifiant seul. Préférez toujours l'extraction depuis le tableau `custom_data`.
</Warning>

## Visibilité dans le dashboard LigdiCash

Le `transaction_id` que vous injectez dans `custom_data` est affiché dans le dashboard LigdiCash, dans le tableau des transactions — aussi bien pour les **payin** que pour les **payout**. Cela vous permet de réconcilier visuellement vos transactions sans avoir à interroger votre propre base de données.

## Exemple de requête complet

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/create \
    -H "Apikey: {API_KEY}" \
    -H "Authorization: Bearer {AUTH_TOKEN}" \
    -H "Accept: application/json" \
    -H "Content-Type: application/json" \
    -d '{
      "commande": {
        "invoice": {
          "items": [{ "name": "Abonnement Pro", "price": 5000, "quantity": 1 }],
          "total_amount": 5000,
          "devise": "XOF",
          "description": "Abonnement Pro — Janvier 2025",
          "customer": "",
          "customer_firstname": "Amadou",
          "customer_lastname": "Diallo",
          "customer_email": "amadou@exemple.com"
        },
        "store": {
          "name": "MonApp",
          "website_url": "https://monapp.com"
        },
        "actions": {
          "cancel_url": "https://monapp.com/paiement/annule",
          "return_url": "https://monapp.com/paiement/succes",
          "callback_url": "https://monapp.com/api/callback/ligdicash"
        },
        "custom_data": {
          "transaction_id": "COMMANDE-20240815-00042"
        }
      }
    }'
  ```

  ```javascript Node.js theme={null}
  import { randomUUID } from "crypto";

  const transactionId = randomUUID(); // ou votre propre format

  // Persistez transactionId en base avec statut "pending" avant ce fetch
  await db.orders.update({ id: orderId, ligdicash_tx_id: transactionId, status: "pending" });

  const response = await fetch(
    "https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/create",
    {
      method: "POST",
      headers: {
        Apikey: process.env.LIGDICASH_API_KEY,
        Authorization: `Bearer ${process.env.LIGDICASH_AUTH_TOKEN}`,
        Accept: "application/json",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        commande: {
          invoice: {
            items: [{ name: "Abonnement Pro", price: 5000, quantity: 1 }],
            total_amount: 5000,
            devise: "XOF",
            description: "Abonnement Pro — Janvier 2025",
            customer: "",
            customer_firstname: "Amadou",
            customer_lastname: "Diallo",
            customer_email: "amadou@exemple.com",
          },
          store: { name: "MonApp", website_url: "https://monapp.com" },
          actions: {
            cancel_url: "https://monapp.com/paiement/annule",
            return_url: "https://monapp.com/paiement/succes",
            callback_url: "https://monapp.com/api/callback/ligdicash",
          },
          custom_data: { transaction_id: transactionId },
        },
      }),
    }
  );

  const data = await response.json();
  // Stockez data.token pour la re-vérification ultérieure
  await db.orders.update({ ligdicash_tx_id: transactionId, ligdicash_token: data.token });
  ```
</CodeGroup>

## Pages associées

* [Tokens et identifiants](/concepts/tokens-et-identifiants) — différences entre `token`, `transaction_id` et `external_id`
* [Sécuriser le callback](/api-paiement/callback/securisation) — pourquoi re-vérifier avec le token stocké
* [Parser custom\_data](/api-paiement/callback/parser-custom-data) — les 3 formes possibles de `custom_data`
