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

# Validation modes

> The six validation modes of direct payin: USSD OTP, USSD Push, Guided USSD, SMS OTP, LigdiCash redirect, Operator redirect. Understand the flows and adapt your UX.

With direct payin, the operator decides how the customer confirms their payment. This mechanism is called the **validation mode**. It defines the exact flow you must implement on the interface side: when to collect an OTP, when to display a waiting message, when to submit a second request, or when to redirect the customer to an external page.

**These six modes are independent.** Each operator uses one (sometimes two with a fallback). You only implement the mode matching the operator you are integrating. See the [operator-specific pages](/en/payment-api/direct-payin/operators/orange-burkina) to know which mode applies.

<Note>
  All modes use **the same endpoint** `POST /pay/v01/straight/checkout-invoice/create`. The difference is on the UX and flow side: whether to collect an OTP, show a waiting message, redirect to a URL returned in `response_text`. Do not confuse this with [hosted payin](/en/payment-api/hosted-payin/introduction), which uses a distinct endpoint (`/redirect/...`) and exposes a LigdiCash multi-operator payment page.
</Note>

## Details of each mode

<Tabs>
  <Tab title="USSD OTP">
    The customer generates an OTP by dialing a USSD code on their phone, **before you call the API**. You collect the number and the OTP at the same time in your form, then submit a single request with both.

    **Flow:**

    <Steps>
      <Step title="The customer dials the USSD code">
        Before filling out your form, the customer dials their operator's USSD code on their phone (e.g. `*144*4*6#` for Orange Burkina Faso). A one-time OTP appears on their screen.
      </Step>

      <Step title="The customer enters their information">
        Your form collects the phone number and the OTP at the same time.
      </Step>

      <Step title="You submit the API request">
        A single request with the number in `customer` and the OTP in `otp`. No second call needed.
      </Step>

      <Step title="Confirmation by callback">
        LigdiCash notifies you of the result via your `callback_url`.
      </Step>
    </Steps>

    ```mermaid theme={null}
    sequenceDiagram
        actor Customer
        participant UI as Your interface
        participant Backend as Your backend
        participant LC as LigdiCash

        Customer->>Customer: Dials the USSD code (*144*4*6# for Orange BF)
        Customer->>Customer: Obtains a one-time OTP
        Customer->>UI: Enters number + OTP in the form
        UI->>Backend: number + OTP
        Backend->>LC: POST /create — customer: number, otp: code
        LC-->>Backend: token (pending transaction)
        LC->>Backend: POST /callback_url (final result)
    ```

    **Recommended UX:** show the USSD code to dial with clear instructions before the customer fills out the form.

    <Note>
      The OTP generated via USSD has a short validity period. Encourage the customer to enter their OTP immediately after generating it.
    </Note>

    **Operators using this mode:** Orange Burkina Faso.
  </Tab>

  <Tab title="USSD Push">
    After your API request, the operator pushes a USSD prompt directly to the customer's phone. The customer validates with their PIN on that prompt. You do not need to resubmit any request.

    **Flow:**

    <Steps>
      <Step title="The customer enters their number">
        Your form only collects the phone number. No OTP to collect.
      </Step>

      <Step title="You submit the API request">
        Request with the number in `customer` and `otp: ""`.
      </Step>

      <Step title="The operator sends the USSD push">
        A USSD menu appears automatically on the customer's phone. They validate the payment with their PIN.
      </Step>

      <Step title="Confirmation by callback">
        LigdiCash notifies you of the result via your `callback_url`.
      </Step>
    </Steps>

    ```mermaid theme={null}
    sequenceDiagram
        actor Customer
        participant UI as Your interface
        participant Backend as Your backend
        participant LC as LigdiCash

        Customer->>UI: Enters their phone number
        UI->>Backend: number
        Backend->>LC: POST /create — customer: number, otp: ""
        LC-->>Backend: token (pending transaction)
        LC->>Customer: USSD push on the customer's phone
        Customer->>Customer: Validates with their PIN
        LC->>Backend: POST /callback_url (final result)
    ```

    **Recommended UX:** after submitting the API request, show a message asking the customer to validate on their phone. Display a waiting indicator and switch to the result when the callback arrives (or via polling the `confirm` endpoint).

    **Operators using this mode:** Moov Africa Burkina Faso, Moov Africa Benin, MTN Mobile Money Benin, Moov Africa Côte d'Ivoire, Vodacom DRC, Airtel DRC, Africell DRC, Airtel Niger, Moov Africa Niger, YAS Togo, Moov Africa Togo.
  </Tab>

  <Tab title="Guided USSD">
    After your API request, the operator sends an SMS to the customer with the instructions and the USSD code to dial. The customer dials this code on their phone to validate. You do not need to resubmit any request.

    **Flow:**

    <Steps>
      <Step title="The customer enters their number">
        Your form only collects the phone number.
      </Step>

      <Step title="You submit the API request">
        Request with the number in `customer` and `otp: ""`.
      </Step>

      <Step title="The operator sends an SMS">
        The customer receives an SMS with the instructions and the USSD code to dial.
      </Step>

      <Step title="The customer dials the USSD">
        The customer follows the instructions in the SMS and validates the payment from their USSD menu.
      </Step>

      <Step title="Confirmation by callback">
        LigdiCash notifies you of the result via your `callback_url`.
      </Step>
    </Steps>

    ```mermaid theme={null}
    sequenceDiagram
        actor Customer
        participant UI as Your interface
        participant Backend as Your backend
        participant LC as LigdiCash

        Customer->>UI: Enters their phone number
        UI->>Backend: number
        Backend->>LC: POST /create — customer: number, otp: ""
        LC-->>Backend: token (pending transaction)
        LC->>Customer: SMS with instructions + USSD code to dial
        Customer->>Customer: Dials the USSD code received by SMS
        LC->>Backend: POST /callback_url (final result)
    ```

    **Recommended UX:** after submitting the API request, show a message telling the customer they will receive an SMS with the instructions to follow.

    **Examples of SMS received by the customer:**

    <CodeGroup>
      ```text MTN Côte d'Ivoire theme={null}
      Vous avez recu une demande de debit avec la reference 1692100357.
      Tapez *133# puis choisissez l option retrait pour approuver.
      ```

      ```text Moov Côte d'Ivoire (fallback) theme={null}
      Vous avez une transaction 0000000433 - LIGDICASH_CI en attente.
      Faites *155*15# pour payer.
      ```
    </CodeGroup>

    <Note>
      This mode is sometimes used as a fallback to USSD Push when the push fails (e.g. Moov Africa Burkina Faso, Moov Africa Côte d'Ivoire).
    </Note>

    **Operators using this mode:** MTN Côte d'Ivoire (primary), Zamani Niger (primary), Moov Africa Burkina Faso (fallback), Moov Africa Côte d'Ivoire (fallback).
  </Tab>

  <Tab title="SMS OTP">
    After your first API request, the operator sends an OTP code by SMS to the customer. The customer shares this code with you, and you submit a second API request with the OTP.

    **Flow:**

    <Steps>
      <Step title="The customer enters their number">
        Your form collects the phone number. No OTP at this stage.
      </Step>

      <Step title="First API request">
        Request with the number in `customer` and `otp: ""`. LigdiCash triggers the OTP being sent by SMS.
      </Step>

      <Step title="The customer receives the OTP">
        The operator sends an OTP code by SMS to the customer's number.
      </Step>

      <Step title="The customer enters the OTP">
        Your interface shows a second input field for the OTP received by SMS.
      </Step>

      <Step title="Second API request">
        You submit the request again, this time with the OTP in the `otp` field.
      </Step>

      <Step title="Confirmation by callback">
        LigdiCash notifies you of the result via your `callback_url`.
      </Step>
    </Steps>

    ```mermaid theme={null}
    sequenceDiagram
        actor Customer
        participant UI as Your interface
        participant Backend as Your backend
        participant LC as LigdiCash

        Customer->>UI: Enters their phone number
        UI->>Backend: number
        Backend->>LC: POST /create — customer: number, otp: ""
        LC-->>Backend: token (pending transaction)
        LC->>Customer: SMS with OTP code
        Customer->>UI: Enters the OTP code received by SMS
        UI->>Backend: OTP
        Backend->>LC: POST /create — customer: number, otp: code
        LC-->>Backend: confirmation
        LC->>Backend: POST /callback_url (final result)
    ```

    **Recommended UX:** use a two-step form — first the number, then the OTP after the SMS is received. Provide a "Resend code" button with a countdown.

    <Warning>
      This mode requires two separate API calls. Wait for the customer to enter the OTP received by SMS before sending the second request.
    </Warning>

    **Operators using this mode:** LigdiCash Wallet.
  </Tab>

  <Tab title="LigdiCash redirect">
    For some operators, the LigdiCash integration currently goes through a **dedicated LigdiCash web page** instead of direct API processing. You call the same `/straight/checkout-invoice/create` endpoint, pre-filling `customer` with the customer's number. LigdiCash responds with a payment page URL where **only the operator matching the number is offered** and the number is pre-filled. You redirect the customer to that URL — they confirm from the LigdiCash page, and the payment proceeds following the operator's own mode.

    <Warning>
      Pre-filling `customer` is **mandatory** for these operators. Without `customer`, the number-based filtering does not work and the payment page does not show the correct operator. Do not confuse this with the other direct payin modes, where `customer` is always provided but simply initiates the transaction without filtering.
    </Warning>

    **Flow:**

    <Steps>
      <Step title="The customer enters their number">
        Your form only collects the phone number. No OTP to collect.
      </Step>

      <Step title="You submit the API request">
        Request to `POST /pay/v01/straight/checkout-invoice/create` with `customer` filled with the customer's number and `otp: ""`.
      </Step>

      <Step title="LigdiCash returns a payment page URL">
        The response contains the transaction token, and the `response_text` field is filled with the URL of the LigdiCash payment page dedicated to this transaction.
      </Step>

      <Step title="You redirect the customer">
        Redirect the customer's browser to the returned URL. The LigdiCash payment page shows the pre-filled number and the operator already selected — the customer cannot change the number or the operator.
      </Step>

      <Step title="Confirmation by callback">
        The customer confirms from the LigdiCash page, and the payment proceeds following the operator's own mode (USSD push, OTP, etc.). LigdiCash notifies you of the final result via your `callback_url`.
      </Step>
    </Steps>

    ```mermaid theme={null}
    sequenceDiagram
        actor Customer
        participant UI as Your interface
        participant Backend as Your backend
        participant LC as LigdiCash
        participant LCWeb as LigdiCash page

        Customer->>UI: Enters their phone number
        UI->>Backend: number
        Backend->>LC: POST /straight/create — customer: number, otp: ""
        LC-->>Backend: token + response_text (LigdiCash page URL)
        Backend-->>UI: LigdiCash page URL
        UI->>LCWeb: Customer redirected
        Customer->>LCWeb: Confirms the payment (number and operator pre-filled)
        LCWeb-->>LC: Validation
        LC->>Backend: POST /callback_url (final result)
    ```

    **Recommended UX:** redirect the customer immediately to the URL returned in `response_text`. Set up a waiting state on the backend in parallel and switch to confirmation when the callback arrives.

    <Note>
      This mode does use the `/straight/checkout-invoice/create` endpoint (direct payin). Do not confuse this with [hosted payin](/en/payment-api/hosted-payin/introduction), which uses a distinct endpoint (`/redirect/...`) and exposes a multi-operator payment page where the customer chooses their payment method.
    </Note>

    **Operators using this mode:** Orange Money DRC, Orange Money Guinea (Conakry), MTN Mobile Money Guinea (Conakry), Orange Money Senegal, Wave Senegal, Free Senegal.
  </Tab>

  <Tab title="Operator redirect">
    For some operators, the LigdiCash integration goes through **the operator's web portal** to authenticate the customer. After your API request, LigdiCash responds with the URL of an external portal (Orange Money, for example). You redirect the customer to that URL — they authenticate there and confirm the payment. The final result arrives by callback.

    **Flow:**

    <Steps>
      <Step title="The customer enters their number">
        Your form only collects the phone number. No OTP to collect.
      </Step>

      <Step title="You submit the API request">
        Request to `POST /pay/v01/straight/checkout-invoice/create` with the number in `customer` and `otp: ""`.
      </Step>

      <Step title="LigdiCash returns a portal URL">
        The response contains the transaction token, and the `response_text` field is filled with the URL of the operator's portal (e.g. `https://mpayment.orange-money.com/...`).
      </Step>

      <Step title="You redirect the customer">
        Redirect the customer's browser to the returned URL. The customer authenticates on the operator's portal and confirms the payment.
      </Step>

      <Step title="Confirmation by callback">
        At the end of the portal journey, LigdiCash notifies you of the result via your `callback_url`. The browser-side return of the customer may be asynchronous — rely on the callback, not on the user-side return.
      </Step>
    </Steps>

    ```mermaid theme={null}
    sequenceDiagram
        actor Customer
        participant UI as Your interface
        participant Backend as Your backend
        participant LC as LigdiCash
        participant Portal as Operator portal

        Customer->>UI: Enters their phone number
        UI->>Backend: number
        Backend->>LC: POST /create — customer: number, otp: ""
        LC-->>Backend: token + response_text (portal URL)
        Backend-->>UI: portal URL
        UI->>Portal: Customer redirected
        Customer->>Portal: Authenticates and confirms
        Portal-->>LC: Payment result
        LC->>Backend: POST /callback_url (final result)
    ```

    **Example create response:**

    ```json theme={null}
    {
      "response_code": "00",
      "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
      "response_text": "https://mpayment.orange-money.com/ml/mpayment/abstract/v1nd8lrkjniiezp3gus76kvmlgztvuylkcs2y5kiir6stko13emvuabrv95oaufb",
      "description": "",
      "custom_data": {
        "transaction_id": "59257c61dab04a70a009af019cd34040",
        "logfile": "202605111744086a021568f2037"
      },
      "wiki": "https://client.ligdicash.com/wiki/createInvoice"
    }
    ```

    **Recommended UX:** redirect the customer immediately to the URL returned in `response_text`. On the backend, set up a waiting state on the transaction in parallel and switch to confirmation when the callback arrives.

    <Warning>
      The customer's browser-side return after the operator portal is not guaranteed — the user may close the tab, lose connectivity, or never return. **The callback remains the only source of truth**, as for the other modes. Never validate the payment based on the customer's return alone.
    </Warning>

    <Note>
      This mode does use the `/straight/checkout-invoice/create` endpoint (direct payin), but the user journey includes an external redirect. Do not confuse this with [hosted payin](/en/payment-api/hosted-payin/introduction), which uses a distinct endpoint and exposes the LigdiCash payment page.
    </Note>

    **Operators using this mode:** Orange Money Côte d'Ivoire, Orange Money Mali.
  </Tab>
</Tabs>

## Summary

| Mode                   | API requests | OTP collected                         | What the customer does                                            |
| ---------------------- | ------------ | ------------------------------------- | ----------------------------------------------------------------- |
| **USSD OTP**           | 1            | Before calling the API (via USSD)     | Dials a USSD, shares the OTP with the merchant                    |
| **USSD Push**          | 1            | None                                  | Validates on the USSD push received                               |
| **Guided USSD**        | 1            | None                                  | Dials a USSD after receiving an SMS                               |
| **SMS OTP**            | 2            | After the first API request (via SMS) | Receives an OTP by SMS, shares it with the merchant               |
| **LigdiCash redirect** | 1            | None                                  | Is redirected to a pre-filtered LigdiCash page and confirms there |
| **Operator redirect**  | 1            | None                                  | Is redirected to the operator's portal and confirms there         |

To know the validation mode of each operator, see its dedicated page in the [Integration by operator](/en/payment-api/direct-payin/operators/orange-burkina) section.
