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 POST. The customer approves on their phone. You poll for the result. Nothing to redirect to, no extra screens to build.
One POST, then the customer reads an OTP off their phone and you POST it back. You need one extra input screen.
One POST returns a paymentUrl. Open it in a browser. You need a returnUrl the customer lands back on.
Every method at a glance
| Method | Shape | Endpoint | Extra field | You get back |
|---|---|---|---|---|
| EcoCash ECOCASH | One-leg express | /api/v1/payments/ecocash/initiate | walletNumber | PENDING — customer approves on their handset |
| InnBucks INNBUCKS | One-leg express | /api/v1/payments/innbucks/initiate | — | innbucksPaymentCode + innbucksDeepLink |
| O'mari OMARI | Two-leg express (OTP) | /api/v1/payments/omari/initiate | walletNumber | AWAITING_CONFIRMATION — then confirm with the OTP |
| SmileCash SMILECASH | Two-leg express (OTP) | /api/v1/payments/smilecash/initiate | walletNumber | AWAITING_CONFIRMATION — then confirm with the OTP |
| Visa / Mastercard CARD | Hosted redirect | /api/v1/payments/card/initiate | returnUrl | paymentUrl — a hosted 3-D Secure page |
| ZimSwitch ZIMSWITCH | Hosted redirect | /api/v1/payments/zimswitch/initiate | returnUrl | paymentUrl — a hosted 3-D Secure page |
| WalletPlus WALLETPLUS | Hosted checkout only | /api/v1/payments/checkout/initiate | returnUrl | paymentUrl — preselect with paymentMethod |
| OneMoney ONEMONEY | Hosted checkout only | /api/v1/payments/checkout/initiate | returnUrl | paymentUrl — 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.
{
"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 expressThe 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.
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"
}'{
"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 expressExpress, 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.
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"
}'{
"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.
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"
}'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.
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 redirectCard 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.
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"
}'{
"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 redirectThe 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 onlyWalletPlus 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.
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.
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.
| Status | What it means |
|---|---|
| PENDING | Raised, customer has not finished. Keep polling. |
| AWAITING_CONFIRMATION | O'mari / SmileCash only — waiting for you to submit the OTP. |
| PAID | Money received. This is sticky — nothing later can un-pay it. |
| FAILED / CANCELED | Terminal. Nothing was taken. |
| UNKNOWN | ZB 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.