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

# Production checklist

> Technical, security, and business checks to run before flipping your LigdiCash integration to production.

This checklist covers the critical items to validate before opening your integration to end customers. Go through it in order — each section builds on the previous one.

## Security

<AccordionGroup>
  <Accordion title="API keys are not exposed on the client side">
    The Apikey and Auth Token must not appear in browser JavaScript, in a mobile app, or in a public Git repository.

    * [ ] Server-side environment variables only (`process.env`, `.env` not committed)
    * [ ] `.gitignore` includes the `.env` file
    * [ ] No key in application logs
    * [ ] No key in API responses returned to the frontend
  </Accordion>

  <Accordion title="The callback re-verifies status via the confirm endpoint">
    Never trust the payload received in the callback — it can be forged. Always call `/confirm` with the token stored at creation.

    * [ ] The callback handler calls `/checkout-invoice/confirm` before any business action
    * [ ] The token used for `confirm` is the one stored at creation (not the callback `token`)
    * [ ] A malformed payload or one with an unknown `transaction_id` is silently ignored
    * [ ] See [Callback security](/en/payment-api/callback/security)
  </Accordion>

  <Accordion title="The callback_url is publicly reachable">
    LigdiCash must be able to reach your callback endpoint from the internet.

    * [ ] URL reachable from an external IP (not `localhost` or a private IP)
    * [ ] HTTPS recommended (HTTP accepted by LigdiCash but discouraged in production)
    * [ ] No firewall rule blocking LigdiCash IPs
    * [ ] If whitelisting is required: contact LigdiCash support for the IPs to allow (max 3 IPs per project)
  </Accordion>

  <Accordion title="Transaction identifiers are non-guessable">
    A sequential `transaction_id` (`1`, `2`, `3`, etc.) lets an attacker enumerate your transactions.

    * [ ] Use a UUID v4 or a prefixed ID with a random suffix (`txn_` + timestamp + random)
    * [ ] See [transaction\_id pattern](/en/concepts/transaction-id-pattern)
  </Accordion>
</AccordionGroup>

## Technical

<AccordionGroup>
  <Accordion title="Hosted payin flow">
    * [ ] `customer` is empty (`""`) in every hosted payin example
    * [ ] `cancel_url` points to the cancellation page, `return_url` to the success page (not swapped)
    * [ ] The payment URL opens in the same tab, a new tab, or a popup — never in an iframe
    * [ ] The frontend verifies the real status via the backend after returning to `return_url` (the redirect is not proof of payment)
  </Accordion>

  <Accordion title="Direct payin flow (if applicable)">
    * [ ] `external_id` and `otp` are empty (`""`) in requests where they are not used
    * [ ] The authentication mode (USSD OTP / SMS / Approval) is correctly documented in the UX for each operator
    * [ ] For SMS OTP mode: the initial request is submitted with `otp: ""`, then resubmitted with the received OTP
    * [ ] For Approval mode (Moov): the callback is the only confirmation — no insufficient polling
  </Accordion>

  <Accordion title="Callback handling">
    * [ ] The handler is idempotent (two calls with the same `transaction_id` only execute the business logic once)
    * [ ] Both callback formats are handled (`application/json` and `application/x-www-form-urlencoded`)
    * [ ] See [Callback idempotency](/en/payment-api/callback/idempotency) and [Parse custom\_data](/en/payment-api/callback/parse-custom-data)
  </Accordion>

  <Accordion title="Database and traceability">
    * [ ] The transaction is created in the database **before** the LigdiCash call
    * [ ] The LigdiCash `token` is stored as soon as the invoice is created
    * [ ] Inbound and outbound logs (LigdiCash requests + received callbacks) are kept
    * [ ] See [Recommended architecture](/en/guides/recommended-architecture)
  </Accordion>

  <Accordion title="Polling fallback">
    * [ ] A periodic job checks transactions stuck in `pending` for more than a few minutes
    * [ ] Polling stops after a reasonable delay (e.g. 24h) — transactions never automatically flip to `notcompleted`
    * [ ] Polling uses the `token` stored at creation, not a token from the callback
  </Accordion>
</AccordionGroup>

## Payout (if applicable)

<AccordionGroup>
  <Accordion title="Payout to LigdiCash wallet">
    * [ ] The recipient has a LigdiCash account (required for `/withdrawal/create`)
    * [ ] The semantics of `top_up_wallet` are correctly implemented: `1` = funds in the wallet, `0` = automatic transfer to the linked mobile money account
  </Accordion>

  <Accordion title="Direct payout to mobile money">
    * [ ] The phone number is in `22670XXXXXXX` format (no `+`, no spaces)
    * [ ] The `/straight/payout` endpoint is used (not `/withdrawal/create`) for recipients without a LigdiCash account
  </Accordion>
</AccordionGroup>

## Business and operations

<AccordionGroup>
  <Accordion title="Merchant account and API project">
    * [ ] The LigdiCash merchant account is active and verified (KYC completed)
    * [ ] The API project is in production mode (not a test account)
    * [ ] The operators you need are enabled on the API project (check in the dashboard)
    * [ ] Visa enabled separately if you need it (specific contract)
  </Accordion>

  <Accordion title="Error handling in the UX">
    * [ ] Every LigdiCash error code is mapped to a user-readable message
    * [ ] The user is clearly informed if their payment fails, with an invitation to retry
    * [ ] See [Common errors](/en/errors/common-errors)
  </Accordion>

  <Accordion title="Communication with LigdiCash">
    * [ ] Your `callback_url` has been tested under real conditions
    * [ ] You have a LigdiCash integration Microsoft Teams group for production emergencies
    * [ ] If IP whitelisting is required: your server IPs have been sent to LigdiCash technical support
  </Accordion>
</AccordionGroup>

## Pre-production testing

Before going live, run at least one end-to-end test for each flow you're integrating.

| Scenario                                 | What to test                                                    |
| ---------------------------------------- | --------------------------------------------------------------- |
| Successful payment                       | Status becomes `completed`, order is confirmed                  |
| Customer cancels the payment             | `cancel_url` is reached, order stays `pending` then expires     |
| Callback received twice                  | Business logic runs only once                                   |
| Transaction still `pending` after 10 min | Fallback polling updates it correctly                           |
| Invalid API key                          | The error is caught and logged, the user sees a generic message |

<Warning>
  LigdiCash does not have a separate sandbox environment. Tests are performed with a real test account provided by the LigdiCash team during the integration period. Contact [developper@ligdicash.com](mailto:developper@ligdicash.com) to obtain test access.
</Warning>

## Related pages

* [Recommended architecture](/en/guides/recommended-architecture) — backend structure
* [Callback security](/en/payment-api/callback/security) — re-verification pattern
* [transaction\_id pattern](/en/concepts/transaction-id-pattern) — identify transactions
* [Error handling](/en/errors/overview) — codes and strategies
