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

# Safaricom M-Pesa

> Configure Safaricom M-Pesa with your own Daraja API keys, using either a paybill or a Buy Goods till.

# Collect payments with Safaricom M-Pesa

`SAFARICOM` collects M-Pesa payments with **your own Daraja API keys**. Centipid
Billing sends an STK push to the payer's phone, the payer enters their M-Pesa
PIN, and money settles into your own paybill or till.

Available to ISPs in **Kenya (KE)**, alongside Kopo Kopo, Bank, Paybill, Till,
Paystack, PesaPal, Relworx, PayPal, and DPO.

If you do not have Daraja API keys, use [Paybill](/billing/payment-gateways/paybill)
or [Till](/billing/payment-gateways/till-number) instead — they send the same
STK push using the platform's Daraja credentials, and money still settles into
your own paybill or till.

## Before you start

1. Have an active Safaricom paybill or Buy Goods till registered to your business.
2. Create an app on the [Daraja portal](https://developer.safaricom.co.ke) and
   collect its **consumer key**, **consumer secret**, and **passkey**. The
   passkey is normally emailed to the address registered in Daraja.
3. Confirm your Daraja app is on **production** credentials, not sandbox.
4. Confirm your workspace has a public HTTPS domain — Safaricom must be able to
   reach it to deliver callbacks, and callback registration is refused without one.

## Configure the gateway

<Steps>
  <Step title="Open Settings → Payments → M-Pesa Paybill / Till Number (API keys)">
    Select the gateway from the payment marketplace to open its form.
  </Step>

  <Step title="Choose the collection method">
    **Paybill** — the payer pays your paybill and enters an account number.
    **Till** — the payer pays your till number (Buy Goods). The rest of the form
    changes with this choice.
  </Step>

  <Step title="Enter the number and the shortcode">
    In paybill mode, **Paybill number** and **M-Pesa shortcode** are the same
    value, so the shortcode field mirrors the paybill and is read-only. In till
    mode, enter your **Till number** and the separate Daraja **M-Pesa shortcode**
    — the shortcode must be different from the till number, or the switch is
    refused.
  </Step>

  <Step title="Enter the Daraja credentials">
    **Consumer key**, **consumer secret**, and **passkey** are all required and
    stored as secrets. They are not echoed back into the form after saving.
  </Step>

  <Step title="Register the callback URLs (paybill only)">
    See [Callback URLs](#callback-urls) below.
  </Step>

  <Step title="Run a controlled test payment">
    Pay a small amount from the captive portal. Verify the STK prompt arrives,
    the payment lands in the ledger, and the subscriber's package is applied.
  </Step>
</Steps>

## Credentials

| Field             | Required             | Notes                                                                                                                                                                      |
| ----------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Collection method | Yes                  | `Paybill` or `Till`. Decides which fields below apply.                                                                                                                     |
| Paybill number    | Yes, in paybill mode | Digits only, at least 4, maximum 20.                                                                                                                                       |
| Till number       | Yes, in till mode    | Digits only, maximum 20.                                                                                                                                                   |
| M-Pesa shortcode  | Yes                  | Digits only. In paybill mode it must equal the paybill (the form fills it for you). In till mode it is the separate Daraja shortcode and must differ from the till number. |
| Consumer key      | Yes                  | Secret. From your Daraja app.                                                                                                                                              |
| Consumer secret   | Yes                  | Secret. From your Daraja app.                                                                                                                                              |
| Passkey           | Yes                  | Secret. Required for STK push; usually emailed to the address registered in Daraja.                                                                                        |

Saving validates the form; it does not prove the credentials work at Safaricom.
Only a test payment does that.

## Callback URLs

Settings → Payments shows two URLs once your workspace has a domain:

| Name             | Path                                           |
| ---------------- | ---------------------------------------------- |
| Confirmation URL | `https://<your-host>/api/safaricom/callback`   |
| Validation URL   | `https://<your-host>/api/safaricom/validation` |

In **paybill** mode the form also shows a **Register callbacks** button, which
registers both URLs with Safaricom's C2B v2 API for you. It is enabled only when
the configuration is saved and complete — while there are unsaved edits it is
disabled with *"Save your changes before registering callbacks."*

Till mode does not offer registration: Buy Goods STK results return on the
callback URL sent with each request, so there is nothing to register.

## What the payer must provide

* **A Safaricom phone number**, which receives the STK prompt.
* **An account number**, in paybill mode. Centipid Billing sends the
  subscriber's account reference with the request, so the payer does not type it.

The payer authorizes on their handset, so no Safaricom domain has to be added to
the captive walled garden.

## How a payment completes

<Steps>
  <Step title="Request">
    Centipid Billing requests a Daraja access token and posts the STK push.
    Both calls run inside the payer's checkout request and are deliberately
    short — a 5-second connect timeout, 10 seconds for the token, and 10 seconds
    for the push acknowledgement — so Safaricom degradation fails fast instead of
    hanging the captive page.
  </Step>

  <Step title="Persistence">
    The acknowledged request is written locally with its `MerchantRequestID` and
    `CheckoutRequestID`. If that write fails, it is retried on a durable queue
    rather than dropped: the push already reached the payer, so the record has to
    survive for the callback to be matched to a checkout.
  </Step>

  <Step title="Authorization">
    The payer enters their M-Pesa PIN. Money moves into your own paybill or till.
  </Step>

  <Step title="Confirmation">
    Safaricom calls back — `/api/safaricom/paybill/callback` in paybill mode, or
    `/api/till-number/callback` in till mode. The callback is processed
    asynchronously, then the payment is recorded and the subscriber's package
    applied.
  </Step>
</Steps>

## Troubleshooting

| Symptom                                                                                                     | What it means                                                                                     | What to do                                                                                                          |
| ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| *"Unable to process payment. Please try again later."* at checkout                                          | The token request or the STK push failed, or Safaricom was unreachable within the timeouts above. | Check the Daraja consumer key, secret, passkey, and shortcode. Confirm the credentials are production, not sandbox. |
| *"Unable to process payment without a resolved payment user."*                                              | Safaricom accepted the push, but the checkout could not be tied to a subscriber.                  | Raise with support with the phone number and the time — the payer may have been charged.                            |
| *"Safaricom authentication failed. Check the saved consumer key and secret."* when registering callbacks    | Daraja refused the credentials.                                                                   | Re-copy both values from the Daraja app. Confirm the app is live.                                                   |
| *"Safaricom rejected the callback registration. Reason: The saved paybill or shortcode is not recognized…"* | The shortcode is not one Daraja recognizes for this app.                                          | Verify the paybill in Daraja and correct it in Settings → Payments.                                                 |
| *"…The callback URLs may already be registered…"*                                                           | Safaricom already holds URLs for this shortcode.                                                  | Check the active URLs in Daraja, or ask Safaricom to reset them.                                                    |
| Callback registration reports both URLs were rejected                                                       | Safaricom would not accept one or both URLs.                                                      | Confirm your workspace domain is a public HTTPS address that resolves from outside your network.                    |
| *"Safaricom could not be reached. Try again."*                                                              | The registration request could not connect.                                                       | Check outbound connectivity from the application host, then retry.                                                  |
| STK prompt never arrives                                                                                    | The push was acknowledged but not delivered.                                                      | Confirm the number is on Safaricom and can receive STK, and that the paybill or till is active.                     |
| Payer paid, service not applied                                                                             | The callback did not arrive or did not match a checkout.                                          | See [Payment troubleshooting](/billing/payment-gateways/troubleshooting).                                           |

For every gateway's fields in one place, see the
[credential reference](/billing/payment-gateways/credentials-reference).
