Ozow Hub
On this page12 sections
Build with AI 2 packages

A build package is every page for one task, with the API operations they use. Copy the prompt into a coding assistant, or hand it the package itself: slim links to each page, full inlines all of them in one document.

  • Migrate a payin from the Payments API to One APIEverything needed to move an existing redirect payin onto One API, with the legacy guide and its One API counterpart side by side.
    View package
  • Migrate refunds from the Payments API to One APIEverything needed to move an existing refunds integration onto One API, with the legacy guide and its One API counterpart side by side.
    View package

This guide maps a redirectRedirect Sending the payer to the Ozow payment page to complete the payment, and returning them to your site afterwards. The alternative is embedding the checkout in your own page, where the payer never leaves it. payinPayin A payment made by a consumer to a merchant. The direction most of this site is about: money coming in. Its counterpart is a payout, which sends money out and is not tied to any payment anyone made you. and refunds integration from the Payments API to One API. It covers what changes, what does not, and the order to make the changes in.

One API is the current integration path. The Payments API continues to process live traffic and is not being switched off on a fixed date, but it does not receive new features, new payment methods are added to One API first.

Before you start

Note

Only users with administrator privileges in the Ozow Dashboard can access the One API Clients section.

What does not change

  • Your site codeSite code The unique code for a site registered under a merchant. A site is a place to transact: a website, or a branch of a store. A merchant can have several, and each transaction names the one it belongs to, so sending the wrong code files the payment against the wrong place. stays the same
  • The payment methods available to your customers, Pay by BankPay by Bank The payer authorises the payment inside their own banking app or online banking, and the funds move from their bank account. No card is involved and no card details are entered., Capitec PayCapitec Pay Capitec's own payment method. The payer gives a cellphone, account or ID number rather than card details, and approves the payment in the Capitec app, so no card number and no banking login is ever entered at checkout. It gets its own button rather than sitting inside the bank list, and it requires Customer Identity Verification.Capitec, Buy Now Pay Later, PayShap RequestPayShap Request The request side of PayShap. Rather than the payer pushing money, the payee asks for it: the payer receives a request and approves it in their own banking app, and the funds move once they do. Enabled by Ozow on request rather than by default.payshap.co.za, are the same products on both APIs
  • Your floatFloat The balance held with Ozow that payouts and refunds are paid out of. Both draw on it, and neither will process while it is empty. Payins do not need one, so if you only take payments you never meet it., settlementSettlement Ozow paying the money you have collected into your bank account. Payins arrive at Ozow first and are settled to you on a schedule, so what a customer paid you today and what has been settled to you today are different amounts. schedule, and Ozow Dashboard reporting are shared across both APIs
  • ZARZAR The ISO 4217 code for the South African rand, and the currency every amount on this site is in unless a page says otherwise. Amounts are decimal rand rather than cents, so 100.00 is one hundred rand. is the only supported currency on either API

What changes

Concept Payments API One API
Authentication API key plus a SHA512SHA-512 A hashing algorithm. Ozow uses it to sign the values in a request or a notification so you can tell that they arrived unaltered and came from us. Hashing is one-way: the hash cannot be turned back into what produced it.Wikipedia hash signed with your private key, per request OAuth 2.0OAuth 2.0 The authorisation framework behind the token endpoint. Ozow uses the client credentials flow: your server exchanges a client ID and secret for a short-lived access token, and sends that token rather than the secret on every subsequent call.RFC 6749 Client Credentials, a bearer tokenBearer token An access token sent in the Authorization header as Authorization: Bearer <token>. Anyone holding the token can use it, which is why it belongs on your server and never in a browser or a mobile app.RFC 6750 from /v1/token
Request shape Flat form fields, mostly PascalCase in notifications, camelCase in requests Nested JSON resources, camelCase throughout
Creating a payment POST /postpaymentrequest returns a url directly POST /v1/payments returns a Payment resource with a redirectUrl and a set of links
Payment outcome The payment request and its outcome are one resource, reported by notification A Payment is the checkout session; each attempt against it is a separate Transaction, retrieved via GET /payments/{id}/transactions
Cancelling before completion Not possible via API, only the customer can abandon the payment page POST /payments/{id}/cancel
Outcome delivery A form-encoded POST to notifyUrl, authenticated with a hash you verify yourself A signed webhookWebhook A URL of yours that Ozow calls when something happens, rather than you polling to find out. The call carries no credential of yours and arrives at a public URL, so authenticate it before acting on it: a hash field on the Payments API, a Svix signature on One API. delivered via Svix, authenticated with svix-id, svix-timestamp, and svix-signature
Refunds Submitted as a batch array to /secure/refunds/submit, one hash check per item POST /transactions/{id}/refunds for a single transaction, or POST /refunds for a batch, both idempotentIdempotency A request is idempotent when sending it twice has the same effect as sending it once. It matters most where a retry after a timeout could otherwise take a payment twice.IETF draft via an Idempotency-Key header
Cancelling a refund Not possible via API POST /refunds/{id}/cancel
Standalone payment method button selectedBankId field in the payment request institutionId field in the payment request
Customer Identity VerificationCustomer Identity Verification Checking that the payment instrument belongs to the natural person making the payment. Ozow requires it for merchants it has classified as high-risk, on Pay by Bank, Absa Pay, Capitec Pay, Nedbank Direct EFT, FNB Payment Requests and PayShap Request, and can disable those methods where it is not implemented correctly. Flat customerIdentifier field Nested payer.identity object with type, country, and identifier
Retrying a failed request safely Not supported, a resubmitted request is a new payment request Idempotency-Key header on POST and PUT requests: replaying the same key returns the original result instead of creating a duplicate
Recurring payments Not available Available, see Recurring payments: One API
Embedded checkout Not available, redirect only Available: iframe, modal, and Wallet SDK

Step 1: Replace hash-based authentication with OAuth

The Payments API signs every request with a SHA512 hash built from your private key. One API instead issues a short-lived bearer token from your Client ID and Client Secret.

Remove your hash-generation code entirely, there is no request hash in One API. Replace it with a token request:

POST Generate Authentication Token Reference
POST https://one.ozow.com/v1/token
Content-Type: application/x-www-form-urlencoded
curl -X POST "https://one.ozow.com/v1/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "scope=payments" \
  -d "grant_type=client_credentials"

A token carries the scopes you asked for and no others, and each scope is refused on the endpoints outside it. Request the scope the calling code needs rather than one token for the whole integration:

What the code is doing Scope Where
Creating a payment, cancelling it, reading its transactions payments Steps 2, 3 and 5
Managing the webhook endpoint and reading its signing secret webhooks Step 4
Issuing and cancelling refunds refunds Step 6

A payments token is refused with 403 Forbidden on the webhook and refund endpoints, so the three parts of your integration hold three tokens. Cache them separately. Asking for a scope your client has not been granted is refused at the token request itself.

Tokens expire after the number of seconds in expires_in, which is 14400, four hours. Read that field rather than hard coding the number: a change to the lifetime reaches you in the response before it reaches this page. Cache the token and request a new one before it expires, rather than requesting one per payment. See Step 1: Obtain an access token for the full flow and code samples in C#, PHP, JavaScript, and Python.

Important

Your private key has no equivalent in One API and is not used for anything. Do not send it, and retire it from your secrets store once the migration is complete and the Payments API integration is decommissioned.


Step 2: Move from flat fields to nested resources

The Payments API request is a single flat object. One API groups related fields, most visibly amount becomes an object with currency and value.

Payments API

{
  "siteCode": "YOUR_SITE_CODE",
  "countryCode": "ZA",
  "currencyCode": "ZAR",
  "amount": "100.00",
  "transactionReference": "ORDER-001",
  "bankReference": "ABC123",
  "cancelUrl": "https://yourstore.com/cancel",
  "errorUrl": "https://yourstore.com/error",
  "successUrl": "https://yourstore.com/success",
  "notifyUrl": "https://yourstore.com/notify",
  "isTest": false,
  "hashCheck": "YOUR_GENERATED_HASH"
}

One API

{
  "siteCode": "YOUR_SITE_CODE",
  "amount": {
    "currency": "ZAR",
    "value": 100.00
  },
  "merchantReference": "ORDER-001",
  "beneficiaryReference": "ABC123",
  "expireAt": "2026-12-31T23:59:59Z",
  "returnUrl": "https://yourstore.com/order-complete"
}

Field mapping

Payments API One API Notes
siteCode siteCode Unchanged
countryCode region Same ISO 3166-alpha-2ISO 3166-1 alpha-2 The two-letter country codes published by the International Organization for Standardization, such as ZA for South Africa and GB for the United Kingdom. Always uppercase.Wikipedia code, renamed and moved to the top level. Optional on One API, and defaults to ZA when you omit it
currencyCode amount.currency Moves inside amount, and must be ZAR
amount (string) amount.value (number) One API takes a numeric value, not a pre-formatted string
transactionReference merchantReference Same purpose, renamed. Still the reference the payer sees on their own statement, and still yours to keep unique
bankReference beneficiaryReference Same purpose, renamed: the reference that appears on your bank statement, for recon. One API is stricter, letters and numbers only where the Payments API also allowed spaces, dashes and punctuation
cancelUrl, errorUrl, successUrl returnUrl One API redirects to a single returnUrl regardless of outcome; determine the outcome from a verified webhook or the transactions endpoint, not from which URL fired
notifyUrl notifyUrl Still accepted, still optional, and still per payment request. Move to webhooks instead: they are configured once and signed, and Step 4 is that change
expiryDateUtc expireAt Now required on every payment request, where the Payments API let you omit it. ISO 8601ISO 8601 The international standard for writing dates and times, such as 2026-03-14T09:30:00Z. Unambiguous about ordering and time zone, which local formats are not.Wikipedia (2026-12-31T23:59:59Z) rather than yyyy-MM-dd HH:mm
isTest Not a request field Test and live are separated by environment (stagingone.ozow.com vs one.ozow.com), not by a flag on the request
hashCheck Not a request field Removed entirely, see Step 1

For the full request and response schema see Create a payment.


Step 3: Update how you read the response and redirect the customer

Payments API response

{
  "paymentRequestId": "00000000-0000-0000-0000-000000000000",
  "url": "https://pay.ozow.com/00000000-0000-0000-0000-000000000000/Secure",
  "errorMessage": null
}

One API response

{
  "links": {
    "self": "https://one.ozow.com/v1/payments/497f6eca-6276-4993-bfeb-53cbbbba6f08",
    "cancel": "https://one.ozow.com/v1/payments/497f6eca-6276-4993-bfeb-53cbbbba6f08/cancel",
    "transactions": "https://one.ozow.com/v1/payments/497f6eca-6276-4993-bfeb-53cbbbba6f08/transactions"
  },
  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  "status": "Created",
  "redirectUrl": "https://pay.ozow.com/497f6eca-6276-4993-bfeb-53cbbbba6f08/secure"
}

Redirect the customer to redirectUrl instead of url. Store id instead of paymentRequestId, you use it to check transactions or cancel the payment later.

Important

Persist that id against your order, before you redirect. On One API the id is the handle to the payment: Get a payment, List its transactions and Cancel it all key off it, where the Payments API keyed off the reference you chose. Store it with the order rather than for the life of the request.

Important

The Payments API returns a rejected request as HTTP 200 with a null url and a reason in errorMessage. One API uses HTTP status codes for this instead, a validation failure comes back as 400 Bad Request with an Error body. Update your error handling to check the status code rather than inspecting the response body for a null URL.

A Payment created but never completed by the customer settles into one of two PaymentStatus values, Created or Expired. This is a separate concept from the transaction outcome, see Step 5.


Step 4: Move from a per-request notifyUrl to a webhook subscription

The Payments API takes a notifyUrl on every payment request and posts a form-encoded notification to it. One API configures a webhook endpoint once, and delivers signed events to it for every subsequent payment.

Set up your webhook endpoint in one of two ways:

  • Via the Ozow Dashboard: navigate to One API Clients, select your client, and manage webhooks from there
  • Via the API: see the webhook endpoints. These need a token with the webhooks scope, and so does Get Webhook Secret below. Your payin token does not reach them

Subscribe to transaction.complete to receive the same outcome your Payments API notifyUrl delivers. Choose the full message type if you need transaction details in the payload, or thin if you will call the API separately for details.

Replace your hash verification with signature verification. Every webhook carries svix-id, svix-timestamp and svix-signature headers, and Verify a webhook signature has the five steps and a working verifier in four languages. Retrieve your webhook secret with Get Webhook Secret.

Important

Never process a webhook notification without first verifying its signature, in exactly the way you never processed a Payments API notification without first verifying its hash. Log and alert on verification failures.

See Step 4: Handle the webhook notification for the full webhook setup and verification process.


Step 5: Re-map transaction statuses

The Payments API reports one flat status on the notification. One API separates the Payment (the checkout session) from each Transaction attempted against it, and the transaction carries the outcome you act on.

Payments API notification statuses

Status Description
Complete Payment completed successfully
Cancelled Customer cancelled the payment
Error An error occurred: check SubStatus for detail
Abandoned Customer left the payment page without completing
Pending The outcome is not known yet and is reposted to your notifyUrl once it is. If you do not use a notifyUrl you receive PendingInvestigation instead
PendingInvestigation Payment is under review: do not credit until resolved

One API transaction statuses

Status Description
Successful The transaction completed, credit the order
Incomplete The customer did not complete the payment attempt
Error The transaction failed. reason carries the detail
Pending The transaction is still in progress
Refunded The transaction completed and has since been refunded

These are not the same enumeration and the values do not line up one to one. Refunded has no Payments API equivalent, because a refund was reported as a separate resource entirely.

Audit every status comparison before you cut over. Error and Pending keep their spelling across both APIs, but Complete becomes Successful. An equality check against Complete therefore returns false for every One API transaction without raising an error, leaving paid orders unfulfilled. Treat Successful as the only status that means the order is paid.

Important

Never update an order status without first verifying the webhook signature, the same rule as the Payments API notification hash.


Step 6: Rebuild refunds on the new endpoints

If you issue refunds, the request and authentication both change alongside the payin flow.

  • Request a token with the refunds scope, the same call as Step 1 with a different scope. This replaces the Payments API's separate /token bearer flow for refunds, and it is a different token from the one your payin code holds
  • Replace the batch array to /secure/refunds/submit with either POST /transactions/{id}/refunds for a single refund or POST /refunds for a batch, see Refunds: One API
  • Add an Idempotency-Key header to every refund request, retrying a failed submission with the same key returns the original result instead of issuing a second refund
  • Drop your per-item hash check, refund requests are authenticated by the bearer token alone
  • refundReason becomes reason, and is still required
  • isRtc becomes realTimePayment
  • Refund statuses keep their spelling but shrink from nine values to six: Pending, Submitted, Complete, Failed, Cancelled and Returned. PendingInvestigation, Invalid and Error do not exist on One API, so any branch you have for those three needs somewhere else to go

Migrating without downtime

Do not attempt a single cutover. Payments API and One API are separate systems, and a payment created on one is not visible on the other.

  1. Build and test the full One API flow in staging: token, payment creation, webhook delivery, and refunds if you use them
  2. Deploy the One API integration behind a flag or a new code path, without removing the Payments API path yet
  3. Route new payments to One API while existing in-flight Payments API payments finish on the old flow
  4. Keep your Payments API webhook handler live until every payment created before the cutover has resolved, a payment can still complete or time out for some time after creation
  5. Once no in-flight Payments API payments remain, decommission the old endpoint calls, retire the private key, and remove the hash-generation code

Important

Test in the staging environment before switching production traffic. Staging credentials and endpoints are entirely separate between the two APIs, a Payments API staging site code does not carry over to One API.


What this guide does not cover

If your Payments API integration uses more than payin and refunds, the rest moves too, and each part has its own guide rather than a step here:

On the Payments API Where it goes
/secure/settlements, /secure/settlements/getsitesettlements Reconcile settlements
/secure/banktransfer/single, /secure/banktransfer/multiple Send a payout
/secure/bulkpaymentrequests/create No One API equivalent, contact support before you migrate

Migrate payin first. The others are separate integrations against separate endpoints, and each can move on its own schedule.


Next steps

In the API reference

6 entries

Last updated