Skip to main content

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.

Your callback URL is a public POST route. Anyone who knows this URL can send a forged payload — with a status: "completed" and an amount of their choosing. If you process this payload without verification, you will fulfill an order or trigger a payout based on a payment that never happened. The golden rule: never act on the received payload. Always re-verify.

The re-verification pattern

1

At creation — store the token

When creating a transaction (payin or payout), store the token returned by the API in your database, linked to your transaction_id.
// Creation response — to be stored
{
  "response_code": "00",
  "token": "{INVOICE_TOKEN}"
}
2

On receiving the callback — extract the identifier

Receive the callback. Extract your transaction_id from the custom_data array by filtering on keyof_customdata.
const entry = payload.custom_data?.find(
  (item) => item.keyof_customdata === "transaction_id"
);
const transactionId = entry?.valueof_customdata;
3

Look up the stored token

Search your database for the token associated with this transaction_id. If no record matches, ignore the callback — it is likely fraudulent.
const stored = await db.transactions.findOne({ transaction_id: transactionId });
if (!stored) return; // unsolicited callback
4

Call the confirm endpoint with the stored token

Call the LigdiCash verification endpoint with the token you stored — not the one in the callback (which is always empty for payins).
// For a hosted payin
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();
5

Act on the result of confirm, not on the payload

Use only the status returned by confirm to decide on your action.
if (result.status === 'completed') {
  await db.orders.markAsPaid(transactionId);
}

What not to do

// DANGEROUS — direct payload processing without verification
app.post('/callback', (req, res) => {
  const { status, amount, external_id } = req.body;

  if (status === 'completed') {
    // Anyone can send this payload
    markOrderAsPaid(external_id, amount);
  }
});
// CORRECT — systematic re-verification
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);
});