> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pesaswap.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Accept your first payment

This page walks through the shortest path to a real M-Pesa Express payment: create the payment, let the customer approve it on their phone, then poll until it settles.

## Prerequisites

You need:

* An **API key** from the Pesaswap dashboard.
* A **`profile_id`** from the dashboard.
* A **phone number** that can receive an M-Pesa STK prompt.

Creating a merchant account, generating an API key, and setting up a connector are covered elsewhere in these docs — this page assumes you already have all three.

<Steps>
  <Step title="Create and confirm the payment">
    Send a `POST` request to `/payments` with `confirm: true`. This creates the payment and immediately attempts to confirm it in one call, so a customer-facing STK prompt is triggered as soon as the request returns.

    <CodeGroup>
      ```bash curl theme={"system"}
      curl --request POST \
        --url https://api.sandbox.pesaswap.io/payments \
        --header "api-key: {{api_key}}" \
        --header "Content-Type: application/json" \
        --data '{
          "amount": 100,
          "currency": "KES",
          "confirm": true,
          "customer_id": "{{customer_id}}",
          "profile_id": "{{profile_id}}",
          "payment_method": "wallet",
          "payment_method_type": "m_pesa_express",
          "phone": "{{phone}}",
          "phone_country_code": "254"
        }'
      ```

      ```javascript Node.js theme={"system"}
      const response = await fetch("https://api.sandbox.pesaswap.io/payments", {
        method: "POST",
        headers: {
          "api-key": "{{api_key}}",
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          amount: 100,
          currency: "KES",
          confirm: true,
          customer_id: "{{customer_id}}",
          profile_id: "{{profile_id}}",
          payment_method: "wallet",
          payment_method_type: "m_pesa_express",
          phone: "{{phone}}",
          phone_country_code: "254",
        }),
      });

      const payment = await response.json();
      console.log(payment);
      ```

      ```python Python theme={"system"}
      import requests

      response = requests.post(
          "https://api.sandbox.pesaswap.io/payments",
          headers={
              "api-key": "{{api_key}}",
              "Content-Type": "application/json",
          },
          json={
              "amount": 100,
              "currency": "KES",
              "confirm": True,
              "customer_id": "{{customer_id}}",
              "profile_id": "{{profile_id}}",
              "payment_method": "wallet",
              "payment_method_type": "m_pesa_express",
              "phone": "{{phone}}",
              "phone_country_code": "254",
          },
      )

      payment = response.json()
      print(payment)
      ```

      ```php PHP theme={"system"}
      <?php
      $curl = curl_init();

      curl_setopt_array($curl, [
          CURLOPT_URL => "https://api.sandbox.pesaswap.io/payments",
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_CUSTOMREQUEST => "POST",
          CURLOPT_HTTPHEADER => [
              "api-key: {{api_key}}",
              "Content-Type: application/json",
          ],
          CURLOPT_POSTFIELDS => json_encode([
              "amount" => 100,
              "currency" => "KES",
              "confirm" => true,
              "customer_id" => "{{customer_id}}",
              "profile_id" => "{{profile_id}}",
              "payment_method" => "wallet",
              "payment_method_type" => "m_pesa_express",
              "phone" => "{{phone}}",
              "phone_country_code" => "254",
          ]),
      ]);

      $response = curl_exec($curl);
      curl_close($curl);

      echo $response;
      ```
    </CodeGroup>

    A successful call returns a `200` with a payment object. The fields that matter right now:

    ```json theme={"system"}
    {
      "payment_id": "pay_...",
      "status": "requires_customer_action",
      "client_secret": "pay_..._secret_...",
      "amount": 100,
      "currency": "KES"
    }
    ```

    **The money has not moved yet.** `status: requires_customer_action` means the STK prompt is now on the customer's phone, waiting for them to act. Nothing settles until they respond.
  </Step>

  <Step title="The customer approves">
    The customer sees the STK push notification on their handset and enters their M-Pesa PIN. Nothing else for you to build here — this happens on their phone, outside your API calls.
  </Step>

  <Step title="Poll for the result">
    Once the customer has approved (or rejected, or ignored) the prompt, retrieve the payment to find out what happened. Use `force_sync=true` so the request checks with the connector instead of returning a stale status.

    <CodeGroup>
      ```bash curl theme={"system"}
      curl --request GET \
        --url "https://api.sandbox.pesaswap.io/payments/{payment_id}?force_sync=true" \
        --header "api-key: {{api_key}}"
      ```

      ```javascript Node.js theme={"system"}
      const response = await fetch(
        `https://api.sandbox.pesaswap.io/payments/${paymentId}?force_sync=true`,
        {
          method: "GET",
          headers: {
            "api-key": "{{api_key}}",
          },
        }
      );

      const payment = await response.json();
      console.log(payment);
      ```

      ```python Python theme={"system"}
      import requests

      response = requests.get(
          f"https://api.sandbox.pesaswap.io/payments/{payment_id}",
          params={"force_sync": "true"},
          headers={"api-key": "{{api_key}}"},
      )

      payment = response.json()
      print(payment)
      ```

      ```php PHP theme={"system"}
      <?php
      $curl = curl_init();

      curl_setopt_array($curl, [
          CURLOPT_URL => "https://api.sandbox.pesaswap.io/payments/{$paymentId}?force_sync=true",
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_CUSTOMREQUEST => "GET",
          CURLOPT_HTTPHEADER => [
              "api-key: {{api_key}}",
          ],
      ]);

      $response = curl_exec($curl);
      curl_close($curl);

      echo $response;
      ```
    </CodeGroup>

    <Warning>
      `force_sync` defaults to `false`. Without it, this endpoint returns the last **stored** status — it does not contact the connector. If you omit `force_sync=true`, your polling loop will keep reading a stale `requires_customer_action` status and will never see the payment settle.
    </Warning>
  </Step>
</Steps>

## Status reference

| Status                     | What it means                                                                     | What to do      |
| -------------------------- | --------------------------------------------------------------------------------- | --------------- |
| `requires_customer_action` | Waiting on the customer to respond to the prompt.                                 | Keep polling.   |
| `processing`               | The connector is finalizing the result.                                           | Keep polling.   |
| `succeeded`                | The payment settled. The money has moved.                                         | Stop — settled. |
| `failed`                   | The customer declined, the PIN was wrong too many times, or the prompt timed out. | Stop — failed.  |

You may also see other values from the full status set (`requires_confirmation`, `requires_payment_method`, `requires_merchant_action`, `cancelled`, `requires_capture`, `partially_captured`, `partially_captured_and_capturable`) depending on the payment method and configuration, but a straightforward M-Pesa Express flow moves through `requires_customer_action` → `processing` → `succeeded` or `failed`.

## Next steps

<CardGroup cols={2}>
  <Card title="Wallet payments" icon="wallet" href="/essentials/payments-guide/wallet-payments">
    M-Pesa Express, Airtel Money, and MTN MoMo in one place.
  </Card>

  <Card title="Card payments and 3DS" icon="credit-card" href="/essentials/payments-guide/card-payments">
    Cards, redirects, and authentication.
  </Card>

  <Card title="Mandates and recurring" icon="repeat" href="/essentials/payments-guide/mandates-and-recurring">
    Save a payment method and charge it later.
  </Card>

  <Card title="Refunds" icon="rotate-ccw" href="/essentials/payments-guide/refunds">
    Send money back to a customer.
  </Card>

  <Card title="Payouts" icon="banknote" href="/essentials/payments-guide/payouts">
    Send money out to a recipient.
  </Card>
</CardGroup>
