Prevail Group of Companies

API Reference

Payment methods

Every rail PrevailPay exposes, and what integrating each one actually costs you. The request body is almost identical across all of them — what differs is the shape: whether the payment finishes in one call, needs a second call for an OTP, or needs you to send the customer to a page.

The three shapes

One-leg express

One POST. The customer approves on their phone. You poll for the result. Nothing to redirect to, no extra screens to build.

Two-leg express (OTP)

One POST, then the customer reads an OTP off their phone and you POST it back. You need one extra input screen.

Hosted redirect

One POST returns a paymentUrl. Open it in a browser. You need a returnUrl the customer lands back on.

Every method at a glance

MethodShapeEndpointExtra fieldYou get back
EcoCash
ECOCASH
One-leg express/api/v1/payments/ecocash/initiatewalletNumberPENDING — customer approves on their handset
InnBucks
INNBUCKS
One-leg express/api/v1/payments/innbucks/initiateinnbucksPaymentCode + innbucksDeepLink
O'mari
OMARI
Two-leg express (OTP)/api/v1/payments/omari/initiatewalletNumberAWAITING_CONFIRMATION — then confirm with the OTP
SmileCash
SMILECASH
Two-leg express (OTP)/api/v1/payments/smilecash/initiatewalletNumberAWAITING_CONFIRMATION — then confirm with the OTP
Visa / Mastercard
CARD
Hosted redirect/api/v1/payments/card/initiatereturnUrlpaymentUrl — a hosted 3-D Secure page
ZimSwitch
ZIMSWITCH
Hosted redirect/api/v1/payments/zimswitch/initiatereturnUrlpaymentUrl — a hosted 3-D Secure page
WalletPlus
WALLETPLUS
Hosted checkout only/api/v1/payments/checkout/initiatereturnUrlpaymentUrl — preselect with paymentMethod
OneMoney
ONEMONEY
Hosted checkout only/api/v1/payments/checkout/initiatereturnUrlpaymentUrl — preselect with paymentMethod

Currency is an ISO-4217 numeric code, not a letter code: 840 = USD, 924 = ZWG. Sending "USD" is rejected.

The part that never changes

Every initiate call — whichever rail — authenticates with your X-SBU-Key and carries the same core body. Method-specific fields are added on top, and the gateway validates them per method.

Common request body
{
  "amount": "10.00",              // required, > 0
  "currencyCode": "840",          // required — ISO 4217 NUMERIC. 840 = USD, 924 = ZWG
  "itemName": "Order #1234",      // required — what the customer is paying for
  "itemDescription": "…",         // optional
  "sbuReference": "INV-1234",     // your own reference; how you reconcile later
  "firstName": "Tendai",
  "lastName": "Moyo",
  "email": "tendai@example.com",
  "mobilePhoneNumber": "+263771234567"
}

sbuReference is worth setting on every call. It is your invoice or order number, it comes back on every read, and it is what lets you reconcile a gateway transaction against your own records if a response is ever lost in flight.

EcoCash

One-leg express

The most common rail in Zimbabwe, and the simplest to integrate. One call pushes an approval prompt to the customer's handset; there is nothing to redirect to and no OTP to collect. walletNumber is the EcoCash number to charge.

Initiate
curl -X POST https://13-247-53-102.sslip.io/api/v1/payments/ecocash/initiate \
  -H 'X-SBU-Key: sbu_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{
    "amount": "10.00",
    "currencyCode": "840",
    "itemName": "Wallet top-up",
    "sbuReference": "INV-1234",
    "walletNumber": "+263771234567"
  }'
Response
{
  "orderReference": "YOURSBU-3FD0A655EB7A4AE7",
  "paymentMethod": "ECOCASH",
  "status": "PENDING",
  "amount": 10.00,
  "currencyCode": "840"
}

Now poll until it settles. The customer has a limited window to approve; if they never do, the payment ends up FAILED.

InnBucks

One-leg express

Express, and the only rail that needs no extra field at all — not even a phone number. Instead of pushing a prompt, it returns a short code the customer keys into the InnBucks app, plus a deep link that opens the app pre-filled.

Initiate
curl -X POST https://13-247-53-102.sslip.io/api/v1/payments/innbucks/initiate \
  -H 'X-SBU-Key: sbu_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{
    "amount": "25.50",
    "currencyCode": "840",
    "itemName": "Order #1234"
  }'
Response
{
  "orderReference": "YOURSBU-FABC5B4FB98A4335",
  "paymentMethod": "INNBUCKS",
  "status": "PENDING",
  "innbucksPaymentCode": "A1B2C3",
  "innbucksDeepLink": "schinn.wbpycode://innbucks.co.zw?pymInnCode=A1B2C3"
}

Show the code and offer the deep link as a button. On a phone the deep link is one tap; the code is the fallback for anyone paying from another device.

O'mari

Two-leg express (OTP)

O'mari is express but takes two legs. The first call charges the wallet and returns AWAITING_CONFIRMATION; the customer receives a one-time code, and you post it back to finish. Budget for an OTP input screen.

Leg 1 — initiate
curl -X POST https://13-247-53-102.sslip.io/api/v1/payments/omari/initiate \
  -H 'X-SBU-Key: sbu_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{
    "amount": "10.00",
    "currencyCode": "840",
    "itemName": "Order #1234",
    "walletNumber": "+263771234567"
  }'
Leg 2 — confirm with the OTP
curl -X POST https://13-247-53-102.sslip.io/api/v1/payments/YOURSBU-3FD0A655EB7A4AE7/confirm \
  -H 'X-SBU-Key: sbu_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{ "otp": "123456" }'

The confirm route only exists for O'mari and SmileCash. Calling it on any other method returns a 400 telling you so.

SmileCash

Two-leg express (OTP)

Identical in shape to O'mari — initiate with walletNumber, then confirm with the OTP on the same /confirm endpoint. Only the rail differs. Note SmileCash is not selectable on the hosted checkout page, so express is the only way to offer it.

Initiate, then confirm
curl -X POST https://13-247-53-102.sslip.io/api/v1/payments/smilecash/initiate \
  -H 'X-SBU-Key: sbu_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{ "amount": "100.00", "currencyCode": "924",
        "itemName": "Order #1234", "walletNumber": "+263771234567" }'

# → status AWAITING_CONFIRMATION, then:

curl -X POST https://13-247-53-102.sslip.io/api/v1/payments/YOURSBU-3FD0A655EB7A4AE7/confirm \
  -H 'X-SBU-Key: sbu_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{ "otp": "123456" }'

Visa / Mastercard

Hosted redirect

Card is notexpress, and cannot be made express. ZB Smile&Pay exposes no raw-card API — every /payments/express-checkout/{card,visa,mastercard} returns 404 — so card payments run through the hosted checkout and come back as a 3-D Secure page URL. PrevailPay hides that: you still call /payments/card/initiate, and the transaction is still recorded as CARD for reporting.

This means you never handle a card number, and you should never try to. The PAN is entered on ZB's page, which keeps PCI scope off your application entirely. returnUrl is required — omit it and you get a 400.

Initiate
curl -X POST https://13-247-53-102.sslip.io/api/v1/payments/card/initiate \
  -H 'X-SBU-Key: sbu_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{
    "amount": "1.00",
    "currencyCode": "840",
    "itemName": "Order #1234",
    "returnUrl": "https://yourapp.example/payment/return"
  }'
Response
{
  "orderReference": "YOURSBU-471D1D78BCB24F8A",
  "paymentMethod": "CARD",
  "status": "PENDING",
  "paymentUrl": "https://zbnet.zb.co.zw/payment_gateway?reference=KVZS7000YZPK&paymentMethod=CARD"
}

Open paymentUrl in a browser (on mobile, an external browser rather than an embedded webview — 3-D Secure steps can break inside webviews). Your returnUrl is where the customer lands afterwards; treat it purely as a UI signal and never as proof of payment. Confirm by polling.

Mastercard needs no separate call — both brands ride the same CARD rail.

ZimSwitch

Hosted redirect

The Zimbabwe national card switch, for local bank cards. Identical to card in every respect — same hosted 3-D Secure page, same required returnUrl — but recorded as ZIMSWITCH so it reports separately from international cards. Use /api/v1/payments/zimswitch/initiate.

WalletPlus, OneMoney, and letting the customer choose

Hosted checkout only

WalletPlus and OneMoney have no express endpoint. They are offered through the standard hosted checkout, where ZB renders the payment page. You can either preselect the method or let the customer pick on the page.

Hosted checkout, method preselected
curl -X POST https://13-247-53-102.sslip.io/api/v1/payments/checkout/initiate \
  -H 'X-SBU-Key: sbu_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{
    "amount": "10.00",
    "currencyCode": "840",
    "itemName": "Order #1234",
    "returnUrl": "https://yourapp.example/payment/return",
    "paymentMethod": "ONEMONEY"
  }'

paymentMethod accepts only what the hosted page can preselect: WALLETPLUS, ECOCASH, INNBUCKS, CARD, OMARI, ONEMONEY. Anything else is a 400. Omit it entirely to let the customer choose — the transaction is then recorded as HOSTED until the callback tells us which rail they actually used, at which point it is rewritten to the real method.

Confirming payment — the same for every method

This is the only step that decides whether a payment succeeded, and it is identical across all rails. PrevailPay receives ZB's callback but does not forward it to SBUs, so polling is the settlement mechanism, not a fallback.

Check status
curl 'https://13-247-53-102.sslip.io/api/v1/payments/YOURSBU-3FD0A655EB7A4AE7?refresh=true' \
  -H 'X-SBU-Key: sbu_your_key_here'

Always pass refresh=true. Without it you get PrevailPay's last-known row; with it, the gateway re-verifies against ZB before answering. Never move money or release goods on a stale read.

StatusWhat it means
PENDINGRaised, customer has not finished. Keep polling.
AWAITING_CONFIRMATIONO'mari / SmileCash only — waiting for you to submit the OTP.
PAIDMoney received. This is sticky — nothing later can un-pay it.
FAILED / CANCELEDTerminal. Nothing was taken.
UNKNOWNZB returned something we do not recognise. Treat as pending and keep polling.

Poll every few seconds while the customer is watching, and also reconcile on a schedule — a customer who closes your app mid-payment still deserves their money. A background job that re-checks anything left pending is not optional; without it, cash ZB took never reaches the customer.

Which should you offer?

Offer EcoCash first — it is the highest-volume rail and the cheapest to build. Add card when you need customers without a mobile wallet, accepting that it costs you a browser hand-off. Add the OTP rails only if your customers actually ask: each one needs a code-entry screen you would not otherwise build.

If you would rather build nothing method-specific at all, use the hosted checkout with no paymentMethod and let ZB present every option. You trade in-app polish for a single integration.