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

# Quickstart — Your first payment in 5 minutes

> Complete an end-to-end hosted payin: create the invoice, redirect the customer, receive the callback, and verify the payment.

This guide walks you through the full flow of a hosted payin: you create an invoice, redirect your customer to the LigdiCash payment page, receive the callback, and verify the status with the `confirm` endpoint.

## Prerequisites

* Your `Apikey` and `API_TOKEN` available in your LigdiCash dashboard
* A `callback_url` publicly reachable from the internet (not `localhost`)

<Note>
  To expose a local server during development, you can use a tool like [ngrok](https://ngrok.com).
</Note>

## Step 1 — Create the transaction

Call the create endpoint, passing your `transaction_id` in `custom_data`. Store the returned `token` — you will need it in step 4.

<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 {API_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": "ORD-2025-00042"
        }
      }
    }'
  ```

  ```javascript Node.js theme={null}
  const transactionId = "ORD-2025-00042";

  // Store transactionId in your database with status "pending" before this call
  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_API_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();

  if (data.response_code !== "00") {
    throw new Error(`LigdiCash error: ${data.response_text}`);
  }

  // Store the token for re-verification in step 4
  await db.orders.update({ ligdicash_tx_id: transactionId, ligdicash_token: data.token });

  const paymentUrl = data.response_text;
  ```

  ```php PHP theme={null}
  $transactionId = "ORD-2025-00042";

  $payload = [
    "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],
    ],
  ];

  $ch = curl_init("https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/create");
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_POST, true);
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Apikey: " . $_ENV["LIGDICASH_API_KEY"],
    "Authorization: Bearer " . $_ENV["LIGDICASH_API_TOKEN"],
    "Accept: application/json",
    "Content-Type: application/json",
  ]);

  $data = json_decode(curl_exec($ch), true);
  curl_close($ch);

  if ($data["response_code"] !== "00") {
    throw new Exception("LigdiCash error: " . $data["response_text"]);
  }

  $paymentUrl = $data["response_text"];
  ```
</CodeGroup>

On success, the response includes the payment page URL in `response_text`:

```json theme={null}
{
  "response_code": "00",
  "token": "eyJ0eXAiOiJKV1Qi...",
  "response_text": "https://app.ligdicash.com/pay/invoice/eyJ0eXAiOiJKV1Qi...",
  "wiki": "https://client.ligdicash.com/wiki/createInvoice"
}
```

## Step 2 — Redirect the customer

The payment URL is in `response_text`. Open it in the same tab or a new tab — **never in an iframe**.

```javascript Node.js theme={null}
// Same tab
window.location.href = paymentUrl;

// New tab
window.open(paymentUrl, "_blank");
```

<Warning>
  Iframes are blocked by LigdiCash. The payment link must open in the same tab, a new tab, a popup, or a native WebView on mobile.
</Warning>

Once the payment is completed or cancelled, LigdiCash redirects the customer to your `return_url` or `cancel_url`.

## Step 3 — Receive the callback

LigdiCash sends a POST request to your `callback_url` when the transaction status changes.

```javascript Node.js (Express) theme={null}
app.post("/api/callback/ligdicash", async (req, res) => {
  // Always respond 200 first as a best practice
  res.status(200).json({ ok: true });

  const payload = req.body;

  // Extract your transaction_id from custom_data
  const entry = payload.custom_data?.find(
    (item) => item.keyof_customdata === "transaction_id"
  );
  const transactionId = entry?.valueof_customdata;

  if (!transactionId) return;

  // Fetch the token stored at creation (step 1)
  const order = await db.orders.findOne({ ligdicash_tx_id: transactionId });
  if (!order) return;

  // Re-verify the status with confirm (step 4)
  await verifyAndUpdateOrder(order);
});
```

<Tip>
  By convention, respond `200` to LigdiCash to acknowledge receipt of the callback.
</Tip>

## Step 4 — Verify with confirm

Never trust the callback payload alone. Call `confirm` with the `token` stored at creation to validate the status.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/confirm/?invoiceToken={TOKEN}" \
    -H "Apikey: {API_KEY}" \
    -H "Authorization: Bearer {API_TOKEN}" \
    -H "Accept: application/json"
  ```

  ```javascript Node.js theme={null}
  async function verifyAndUpdateOrder(order) {
    const response = await fetch(
      `https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/confirm/?invoiceToken=${order.ligdicash_token}`,
      {
        headers: {
          Apikey: process.env.LIGDICASH_API_KEY,
          Authorization: `Bearer ${process.env.LIGDICASH_API_TOKEN}`,
          Accept: "application/json",
        },
      }
    );

    const data = await response.json();

    if (data.status === "completed") {
      await db.orders.update({ id: order.id, status: "paid" });
    } else if (data.status === "notcompleted") {
      await db.orders.update({ id: order.id, status: "failed" });
    }
    // If "pending": do nothing, wait for the next callback
  }
  ```
</CodeGroup>

## What's next?

Your first payment works. To go further:

<CardGroup cols={2}>
  <Card title="Direct payin" icon="mobile" href="/en/payment-api/direct-payin/introduction">
    Initiate the payment directly from your interface, without leaving your app.
  </Card>

  <Card title="Secure the callback" icon="shield" href="/en/payment-api/callback/security">
    The complete pattern for re-verification and deduplication.
  </Card>

  <Card title="Payout" icon="arrow-right" href="/en/payment-api/payout/introduction">
    Send transfers to your customers or partners.
  </Card>

  <Card title="All operators" icon="globe" href="/en/reference/supported-operators">
    Orange Money, Moov, MTN, Wave, and more.
  </Card>
</CardGroup>
