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

# ZenoPay

> Configure ZenoPay mobile-money collections for Tanzania, with one API key.

# Collect payments with ZenoPay

`ZENOPAY` pushes a payment request to the payer's mobile-money wallet. The payer
authorizes it on their handset, and the order is polled to completion.

Available to ISPs in **Tanzania (TZ)**, alongside ClickPesa, AzamPay, PalmPesa,
PawaPay, PesaPal, Relworx, PayPal, and DPO.

## Before you start

1. Complete ZenoPay merchant onboarding in an account owned by the ISP.
2. Collect the **API key** from the ZenoPay merchant dashboard.
3. Confirm your workspace is reachable from the public internet, so ZenoPay can
   deliver its webhook.

## Configure the gateway

<Steps>
  <Step title="Open Settings → Payments → ZenoPay">
    Select ZenoPay from the payment marketplace.
  </Step>

  <Step title="Enter the API key">
    **API key** (required) is stored as a secret and is not echoed back into the
    form after saving. It is sent as the `x-api-key` header on every request.
  </Step>

  <Step title="Leave the API host blank unless ZenoPay told you otherwise">
    **API host** (optional) overrides the endpoint Centipid Billing calls. Leave
    it empty to use the default ZenoPay host.
  </Step>

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

## Credentials

| Field    | Required | Notes                                                                                      |
| -------- | -------- | ------------------------------------------------------------------------------------------ |
| API key  | Yes      | Secret. From the ZenoPay merchant dashboard. The only field required to switch to ZenoPay. |
| API host | No       | Falls back to the default ZenoPay host. Enter a full URL.                                  |

## Callback URLs

The webhook address is sent with every payment request, so there is nothing to
register in the ZenoPay dashboard. Settings → Payments still shows it under
**Callback URLs** (`https://<your-host>/api/zenopay/webhook`) so you can confirm
the address ZenoPay will call, and check it is not blocked by a firewall or WAF
rule.

## What the payer must provide

* **Email address (required).** ZenoPay rejects an order without one, so the
  **captive portal** asks the payer for an email when your account is on
  ZenoPay. The **customer portal** uses the email on the subscriber's profile,
  so add one there before directing a subscriber to the portal. Without a valid
  email, checkout stops with *"A valid email address is required for ZenoPay
  payments"* before any request reaches ZenoPay.
* **Tanzanian phone number (required).** Accepted in local or international
  form; it is normalized before it is sent.
* **Amount.** Rounded to a whole number before the request is sent.

Because authorization happens on the payer's handset rather than in a hosted
checkout page, no ZenoPay domain needs to be added to the captive walled garden.

## How a payment completes

<Steps>
  <Step title="Request">
    Centipid Billing posts the order to
    `/api/payments/mobile_money_tanzania` with an **order id it generates
    itself**. That id is the local transaction reference, so the handle
    everything reconciles against exists before ZenoPay answers.
  </Step>

  <Step title="Authorization">
    The payer approves the request on their phone.
  </Step>

  <Step title="Confirmation">
    A background job polls `/api/payments/order-status` every 5 seconds, for up
    to 40 attempts (about 3 minutes 20 seconds), until the order reaches a
    terminal state. **This poller — not the webhook — is what completes the
    payment**, so a payment still completes when the payer closes the captive
    browser mid-checkout.
  </Step>

  <Step title="Provisioning">
    On `COMPLETED`, a payment is recorded with method **ZenoPay** and the
    subscriber's package is applied asynchronously. On `FAILED`, the reason is
    stored against the transaction and nothing is credited.
  </Step>
</Steps>

<Warning>
  ZenoPay's webhook carries no signature, HMAC, or shared secret in its body, so
  it cannot prove it came from ZenoPay. Centipid Billing treats it as a hint
  only: nothing is credited from the webhook body. Every credit is made against
  a status read from ZenoPay with your own API key, so a spoofed "completed"
  webhook cannot provision a subscriber.
</Warning>

A failed request is deliberately **not** retried automatically. An automatic
retry after a lost response could raise a second prompt on the same order and
charge the payer twice. The payer retries by choice instead.

## Troubleshooting

| Symptom                                                              | What it means                                                       | What to do                                                                                                               |
| -------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| *"ZenoPay API key not configured."*                                  | No API key is saved.                                                | Enter it in Settings → Payments → ZenoPay.                                                                               |
| *"ZenoPay authentication failed. Check the API key."*                | ZenoPay rejected the key.                                           | Re-copy it from the merchant dashboard. Confirm the account is live.                                                     |
| *"A valid email address is required for ZenoPay payments."*          | Checkout had no valid email for the payer.                          | Ask the payer for an email at checkout, or set one on the subscriber's profile.                                          |
| *"A valid Tanzanian phone number is required for ZenoPay payments."* | The number could not be normalized.                                 | Re-enter it in local or international form.                                                                              |
| *"Could not reach ZenoPay. Please try again."*                       | The request could not reach ZenoPay.                                | Check outbound connectivity from the application host, and any custom **API host** value.                                |
| A message from ZenoPay on a failed request                           | ZenoPay refused the order and its own reason is shown.              | Read the message — it usually names the field at fault.                                                                  |
| Payer paid, but the transaction sits pending                         | The order never reached a terminal state within the polling window. | Confirm the payment on the ZenoPay dashboard, then raise it with support with the order id and the payer's phone number. |

For symptoms that are not specific to ZenoPay, see
[Payment troubleshooting](/billing/payment-gateways/troubleshooting). For every
other gateway's fields, see the
[credential reference](/billing/payment-gateways/credentials-reference).
