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

# The transaction_id pattern: the golden rule

> Why generate and store your own transaction identifier before every LigdiCash API call — and how to retrieve it in the callback and the dashboard.

When you create a transaction through the LigdiCash API, you receive a `token` in return. This token **is not stable**: its value in the callback differs from the one obtained at creation. If you rely on it to identify your transactions, you will lose the thread between what you initiated and what you confirmed.

The solution is simple: generate your own identifier on the merchant side, store it **before** calling the API, and pass it in the `custom_data` field. LigdiCash will return it intact in the callback, and display it in your dashboard.

## The problem: the token changes between creation and callback

When a transaction is created, the API returns a `token`:

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

When LigdiCash notifies you of the result via the callback, this same field contains a **different value**:

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

Both tokens point to the same transaction, but you cannot link them directly. Without an identifier of your own, reconciliation is impossible.

## The solution: inject your transaction\_id into custom\_data

The `custom_data` field of each request is a **JSON object** whose keys you define freely. This is where you inject your identifier:

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

In the callback, LigdiCash transforms this object into an **array**. Your `transaction_id` appears in this form:

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

Filter on `keyof_customdata` to extract your value — do not rely on the position in the array, other entries may be present.

### transaction\_id format

You are free to choose the format that fits your business:

| Format              | Example                                | Typical use                            |
| ------------------- | -------------------------------------- | -------------------------------------- |
| Order reference     | `ORD-2026-00042`                       | E-commerce — displayed to the customer |
| Short readable code | `PAY-8KXZ`                             | Customer support, memorable            |
| UUID v4             | `f47ac10b-58cc-4372-a567-0e02b2c3d479` | Distributed systems, internal use      |
| Timestamp + random  | `1723718400-a3f9`                      | Job queue                              |
| Internal ID         | `order_8821`                           | Existing application                   |

<Tip>
  If you display the `transaction_id` to your customers — for example on the receipt or in a tracking UI — prefer a short, readable format like `ORD-2026-00042`. It makes exchanges with your support easier: the customer can dictate or copy it without error.
</Tip>

<Tip>
  If the `transaction_id` stays purely internal, a UUID v4 is a good default: guaranteed unique without coordination between servers.
</Tip>

<Warning>
  Avoid purely sequential identifiers exposed to the customer (e.g. `order_1`, `order_2`) — they reveal your transaction volume. Prefer a prefix + non-trivial counter, or a random identifier.
</Warning>

## Recommended lifecycle

<Steps>
  <Step title="Generate the transaction_id">
    Before calling the API, generate your unique identifier server-side.
  </Step>

  <Step title="Persist it in your database">
    Save it in your database with the `pending` status **before** calling the API. If the call fails, you still have a trace.
  </Step>

  <Step title="Pass it in custom_data">
    Include it in the `custom_data` array of your create request.
  </Step>

  <Step title="Store the creation token">
    Also store the `token` returned by the API — it will be needed to call the `confirm` endpoint when re-verifying the callback.
  </Step>

  <Step title="Retrieve it in the callback">
    When LigdiCash notifies you, extract your `transaction_id` from `custom_data` and update the status in your database.
  </Step>
</Steps>

## Retrieving the transaction\_id in the callback

The callback payload contains a `custom_data` field. Its shape varies depending on the flow (see [Parsing custom\_data](/en/payment-api/callback/parse-custom-data)), but for a payin it is an array:

<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>
  The callback also contains a `transaction_id` field **at the root** of the payload. LigdiCash builds it by concatenating the `valueof_customdata` of all entries whose key contains `id` (e.g. `transaction_id`, `partner_id`, `event_id`...), separated by `;`. If you injected several fields of this type, this root value will be a mix — not your identifier alone. Always prefer extraction from the `custom_data` array.
</Warning>

## Visibility in the LigdiCash dashboard

The `transaction_id` you inject in `custom_data` is displayed in the LigdiCash dashboard, in the transaction table — for both **payin** and **payout**. This lets you reconcile your transactions visually without having to query your own database.

## Complete request example

<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": "Pro Subscription", "price": 5000, "quantity": 1 }],
          "total_amount": 5000,
          "devise": "XOF",
          "description": "Pro Subscription — January 2025",
          "customer": "",
          "customer_firstname": "Amadou",
          "customer_lastname": "Diallo",
          "customer_email": "amadou@example.com"
        },
        "store": {
          "name": "MyApp",
          "website_url": "https://myapp.com"
        },
        "actions": {
          "cancel_url": "https://myapp.com/payment/cancel",
          "return_url": "https://myapp.com/payment/success",
          "callback_url": "https://myapp.com/api/callback/ligdicash"
        },
        "custom_data": {
          "transaction_id": "ORDER-20240815-00042"
        }
      }
    }'
  ```

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

  const transactionId = randomUUID(); // or your own format

  // Persist transactionId in your database with status "pending" before this 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: "Pro Subscription", price: 5000, quantity: 1 }],
            total_amount: 5000,
            devise: "XOF",
            description: "Pro Subscription — January 2025",
            customer: "",
            customer_firstname: "Amadou",
            customer_lastname: "Diallo",
            customer_email: "amadou@example.com",
          },
          store: { name: "MyApp", website_url: "https://myapp.com" },
          actions: {
            cancel_url: "https://myapp.com/payment/cancel",
            return_url: "https://myapp.com/payment/success",
            callback_url: "https://myapp.com/api/callback/ligdicash",
          },
          custom_data: { transaction_id: transactionId },
        },
      }),
    }
  );

  const data = await response.json();
  // Store data.token for later re-verification
  await db.orders.update({ ligdicash_tx_id: transactionId, ligdicash_token: data.token });
  ```
</CodeGroup>

## Related pages

* [Tokens and identifiers](/en/concepts/tokens-and-identifiers) — differences between `token`, `transaction_id`, and `external_id`
* [Securing the callback](/en/payment-api/callback/security) — why re-verify with the stored token
* [Parsing custom\_data](/en/payment-api/callback/parse-custom-data) — the 3 possible shapes of `custom_data`
