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

# Polling vs callback

> When to use polling (active checking) and when to rely on the callback (passive notification) — benefits, limits, and recommended hybrid pattern.

There are two ways to know whether a LigdiCash transaction succeeded: **wait for LigdiCash to notify you** (callback), or **query the API at regular intervals** (polling). Both approaches are complementary — using one without the other leaves a blind spot.

## Definitions

**Callback (passive notification)**

LigdiCash sends a POST request to your `callback_url` as soon as the status of a transaction changes. You do not have to trigger anything: LigdiCash comes to notify you.

**Polling (active checking)**

Your backend periodically calls the `confirm` endpoint with the transaction's token until it obtains a terminal status (`completed` or `notcompleted`). You query LigdiCash instead of waiting for it to reach out.

## Benefits and limits

|                         | Callback                                      | Polling                                     |
| ----------------------- | --------------------------------------------- | ------------------------------------------- |
| **Latency**             | Low — near-instant notification               | Variable — depends on the chosen interval   |
| **Server load**         | Minimal — one incoming call                   | Repeated — N outgoing calls per transaction |
| **Network reliability** | Fragile — your server must be reachable       | Robust — you initiate the calls             |
| **Local environment**   | Difficult — requires a tunnel (ngrok, etc.)   | Easy — works anywhere                       |
| **Security**            | Requires re-verification with `confirm`       | Native — you query the API directly         |
| **Slow transactions**   | Ideal — notified as soon as something changes | Costly — keeps polling during the wait      |

## Recommendations by use case

**Use the callback as the primary strategy**

The callback is the mechanism designed by LigdiCash for production notification. It is reactive, low-cost, and suited to transactions whose confirmation delay is variable (from a few seconds to several minutes depending on the operator).

<Warning>
  LigdiCash sends **two POST requests** for each callback: one in `application/x-www-form-urlencoded` and one in `application/json`. Your handler must deduplicate — see [Callback idempotency](/en/payment-api/callback/idempotency).
</Warning>

**Use polling in local development**

Locally, your server is not reachable from the internet. Use polling to simulate production behavior without having to set up a tunnel.

**Use polling as a safety net in production**

Even in production, a callback may not arrive: your server was temporarily unavailable, the network dropped, or LigdiCash encountered an error sending the notification. A fallback polling triggered a few minutes after the creation of a still `pending` transaction lets you catch these cases.

**Do not replace the callback with polling**

Fast polling (every second) to replace the callback is a bad idea: it multiplies API calls, increases perceived latency, and creates concurrency issues if two workers process the same transaction.

## Recommended hybrid pattern

The most robust architecture combines the two: **primary callback + fallback polling**.

```
Transaction created
       │
       ├─► Store the token in DB (status = "pending")
       │
       ├─► [Callback] LigdiCash notifies → re-verify with confirm → update
       │
       └─► [Fallback] Job scheduled after N minutes:
               If status is still "pending" → call confirm
               If "completed" or "notcompleted" → update and stop
               If still "pending" after timeout → mark as "expired" and alert
```

```javascript JavaScript — fallback polling theme={null}
async function checkPendingTransaction(token) {
  const MAX_ATTEMPTS = 10;
  const INTERVAL_MS = 5000; // 5 seconds between calls

  for (let i = 0; i < MAX_ATTEMPTS; i++) {
    const res = await fetch(
      `https://app.ligdicash.com/pay/v01/redirect/checkout-invoice/confirm/?invoiceToken=${token}`,
      {
        headers: {
          Apikey: process.env.LIGDICASH_API_KEY,
          Authorization: `Bearer ${process.env.LIGDICASH_API_TOKEN}`,
          Accept: "application/json",
        },
      }
    );
    const data = await res.json();

    if (data.status === "completed" || data.status === "notcompleted") {
      return data; // terminal status reached
    }

    await new Promise((r) => setTimeout(r, INTERVAL_MS));
  }

  // After MAX_ATTEMPTS without a terminal status
  return { status: "timeout" };
}
```

<Tip>
  In production, trigger this polling from a background job (cron, worker queue) rather than from the HTTP request thread. This avoids blocking the response to the user during the wait.
</Tip>

## Security: mandatory re-verification

Whether you use the callback or polling, **never fulfill an order without calling `confirm`** with the token stored at creation. A forged callback payload can be sent by anyone who knows your URL. `confirm` is the only source of truth.

<Note>
  See [Securing the callback](/en/payment-api/callback/security) for the full re-verification pattern with idempotency handling.
</Note>

## Related pages

* [Recommendations](/en/payment-api/status-verification/recommendations) — polling intervals, timeouts, pending handling
* [Callback — security](/en/payment-api/callback/security) — re-verification with `confirm`
* [Callback — idempotency](/en/payment-api/callback/idempotency) — deduplicating the two requests
* [Response codes and statuses](/en/concepts/response-codes-and-statuses) — full reference of `status` values
