Ozow Hub
On this page14 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.

  • Take a paymentEverything needed to take a payment end to end with One API, from credentials through the hosted page to the webhook that confirms it, and the test cases that prove each outcome before you go live.
    View package
  • 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

This guide walks you through 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. integration using One API. Your system creates a payment request, redirects the customer to Ozow's secure hosted payment page, and receives a 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. notification when the payment is complete.

This guide uses One API: Ozow's recommended API for all new integrations. It uses 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 for authentication, and new payment methods and features are released here first.

Already integrated? If your integration posts to api.ozow.com and builds 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, you're on the Payments API: see Redirect to Ozow under Legacy integrations, or Migrating to One API.

Before you start

  • You have completed Prerequisites and onboarding
  • You have a Client ID and Client Secret from the One API Clients section of your Ozow Dashboard
  • You have 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. from the Site section of your Dashboard
  • Your webhook endpoint is set up and publicly accessible via HTTPS

Note

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

Environments

Environment Token endpoint API base URL Dashboard
Production https://one.ozow.com/v1/token https://one.ozow.com/v1 dash.ozow.com
Staging https://stagingone.ozow.com/v1/token https://stagingone.ozow.com/v1 stagingdash.ozow.com

How redirect works


Core integration

Step 1: Obtain an access token

One API uses OAuth 2.0 Client Credentials authentication. You need an access token before making any API calls.

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"

Successful response

{
  "access_token": "mF_9.B5f-4.1JqM",
  "token_type": "Bearer",
  "expires_in": "14400",
  "scope": "payments"
}

Store the access_token and include it in the Authorization header of all subsequent requests:

Authorization: Bearer YOUR_ACCESS_TOKEN

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. If authentication fails, the API returns 401 Unauthorized.


Step 2: Create a payment request

Create a payment request for your customer's order.

POST Request Payment Reference
POST https://one.ozow.com/v1/payments
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
curl -X POST "https://one.ozow.com/v1/payments" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "siteCode": "YOUR_SITE_CODE",
    "amount": {
      "currency": "ZAR",
      "value": 100.00
    },
    "merchantReference": "ORDER-001",
    "beneficiaryReference": "ONLINESHOP002",
    "expireAt": "2026-12-31T23:59:59Z",
    "returnUrl": "https://yourstore.com/order-complete"
  }'

Key request fields

Field Type Required Description
siteCode string Yes Your Ozow site code
amount.currency string Yes Must be ZAR
amount.value number Yes Payment amount
merchantReference string Yes Your internal order reference
beneficiaryReference string Usually The reference that appears on your bank statement for the payment. Letters and numbers only
expireAt string Yes Payment request expiry in RFC 3339RFC 3339 A profile of ISO 8601 for timestamps on the internet, and what most APIs mean when they say a field is an ISO date.IETF format
returnUrl string Yes URL to redirect the customer to after payment

Important

Send beneficiaryReference. The contract marks it optional because a site can be configured either way, and most are configured to require it. Leaving it out of a site that wants it answers 400 with Error occurred creating merchant request (Parameter 'Bank reference missing'), which does not name the field it means.

For the full list of request fields see Create a payment.

Successful 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"
}

Step 3: Redirect the customer

Redirect your customer's browser to the redirectUrl from the response. Ozow displays the payment page where the customer selects their preferred payment method and completes the payment.

Send them to the URL you were given, and do not build one. Its shape is Ozow's to change, and a URL assembled from the payment id is a URL that stops working without notice.

status on the response is Created. Compare it without case.

Once the customer completes or cancels the payment, Ozow redirects them back to your returnUrl.

Note

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. is available by default. Additional payment methods such as 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, and 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 enabled by Ozow on request. Once activated, they appear automatically on the payment page with no additional integration work required.

Important

Do not use the customer's return to your returnUrl as confirmation that a payment was successful. Always confirm payment status via a verified webhook notification or an API status check.


Step 4: Handle the webhook notification

Ozow sends a webhook notification to your endpoint when a transaction completes. One API uses Svix to deliver webhooks.

There are two ways to be told, and you choose them independently.

Webhooks notifyUrl
How you turn it on The Ozow Dashboard or the webhook endpoints Set notifyUrl on the payment request
What arrives The events below, signed with Svix headers The same notification the Payments API sends on a payin, with the same payload and the same hash
When it fires Every transaction Every payment where you set notifyUrl

Webhooks are the recommended path and the rest of this step covers them. notifyUrl is optional and exists so that an integration already handling the Payments API notification keeps working: point it at your existing handler and the payload and hash are the ones it already verifies.

Important

If you configure both, both fire for the same payment. Your handlers must be 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, keyed on the merchant reference or the transaction ID, or one payment marks an order paid twice.

If you set neither, nothing is delivered and you must poll Get Transactions instead, which is slower and which Step 5 covers.

Setting up your webhook endpoint

Set up your webhook endpoint in one of two ways:

Important

List before you create. Creating a subscription that duplicates one you already have delivers every event twice, and the second delivery is indistinguishable from the first, so a handler that is not idempotent processes the same payment again. A deploy script that creates a subscription on every run is the usual way this happens.

Available webhook events

Event Description
transaction.complete A transaction has completed: check the status field to determine if it was successful or resulted in an error
refund.complete A refund has completed: check the status field for the refund result
subscription.authorization.success A customer approved a subscription consent ⚠️ Beta, subject to change
subscription.authorization.failed A consent was not approved ⚠️ Beta, subject to change
subscription.transaction.success An individual collection succeeded ⚠️ Beta, subject to change
subscription.transaction.failed An individual collection failed ⚠️ Beta, subject to change
subscription.completed A subscription took all its scheduled occurrences ⚠️ Beta, subject to change
subscription.canceled A subscription was cancelled ⚠️ Beta, subject to change. One l, unlike Cancelled elsewhere
subscription.expired An authorisation lapsed before the subscription became active ⚠️ Beta, subject to change

Subscribe to the name exactly as written. An event name the service does not know is rejected, so a subscription to something close is a subscription that never fires.

Message types

When setting up your webhook subscription you can choose how much data is included in each notification:

Message type What it includes
thin id, status and reason
full The transaction's fields, in the same shape the Payments API notification uses

Choose full if you need transaction details in the webhook payload. Choose thin if you only need the status and will query the API for details separately.

Important

full is implemented for transaction.complete and refund.complete only. A subscription event registered as full delivers nothing.

What arrives

Every delivery has the same envelope. data is WebhookEventData when the subscription asked for thin, TransactionCompleteFullData for a full subscription to transaction.complete, and RefundCompleteFullData for a full subscription to refund.complete. Every value in a full payload is a string, the amount and the flags included.

{
  "type": "transaction.complete",
  "timestamp": "2026-03-14T09:30:00Z",
  "data": {
    "id": "00000000-0000-0000-0000-000000000000",
    "status": "Successful",
    "reason": null
  }
}

id is the transaction, refund or subscription the event is about. reason carries the status message and is null when there is nothing to say.

The status values for transaction.complete

These are not the transaction statuses on the statuses page: the webhook maps them down to four.

status Sent when the transaction is
Successful Complete
Incomplete Created
Pending Pending or PendingInvestigation
Error anything else, including Cancelled, Abandoned, Voided and Unknown

A cancelled payment arrives as Error, not as Cancelled. reason tells you which it was. Switch on these four and read reason for the detail; do not expect the status names the transaction itself carries.

For refund.complete, status is Pending, Failed, Complete, Submitted, Cancelled, Returned or Invalid. Those are the thin values. A full subscription carries the refund's own status in Status, unmapped, so PendingInvestigation and Error arrive as themselves rather than as Pending and Failed. Handle both if you are on full.

Verifying the webhook signature

Every webhook notification includes Svix signature headers. You must verify the signature before acting on any notification.

Header Description
svix-id Unique message identifier, the same if the webhook is resent after a failure
svix-timestamp Timestamp in seconds since epoch
svix-signature Base64Base64 A way of writing binary data using ordinary text characters, so it can travel inside JSON or a URL. It is an encoding, not encryption: anyone can decode it.Wikipedia encoded signature

Verify a webhook signature has the five steps of the check and a working verifier in csharp, php, python and javascript, with or without the Svix library. To retrieve the secret for your webhook, use Get Webhook Secret.

Important

Never process a webhook notification without first verifying its signature. Log and alert on verification failures: do not silently discard them.


Step 5: Confirm the transaction outcome

After verifying the webhook, check the transaction status and update your order.

You can also check transaction status directly via the API at any time:

GET List Transactions for Payment Reference
GET https://one.ozow.com/v1/payments/{id}/transactions
Authorization: Bearer YOUR_ACCESS_TOKEN

Replace {id} with the payment ID returned in Step 2.

Note

Handle duplicate webhook notifications idempotently. Ozow may send the same notification more than once. Processing the same notification twice must not result in double-crediting an order.


Step 6: Cancel a payment

If your customer abandons checkout or you need to cancel an order before the customer completes payment, you can cancel the payment request using the cancel link returned in the payment response.

POST Cancel Payment Request Reference
POST https://one.ozow.com/v1/payments/{id}/cancel
Authorization: Bearer YOUR_ACCESS_TOKEN

A successfully cancelled payment will no longer be accessible to the customer via the redirectUrl. If the customer attempts to use the link after cancellation they will see an error.

Important

You can only cancel a payment that has not yet been completed. Do not attempt to cancel a payment with a complete status: use the refunds flow instead. See the One API reference for details.


Optional features

Standalone button

A standalone button lets you surface a specific Ozow payment method as a dedicated button on your checkout page. Instead of showing a generic payment page where the customer selects their payment method, a standalone button takes the customer directly to a specific payment method; for example Capitec Pay, Buy Now Pay Later, or PayShapPayShap South Africa's rapid payments service, run by the banks. Low-value payments clear in seconds, and the recipient can be identified by a ShapID instead of by an account number.payshap.co.za.

Adding a standalone button to your checkout removes an extra step for the customer, increases awareness of specific payment methods, and can improve conversion rates.

Important

Do not display a standalone button for a payment method until you have received confirmation from Ozow that your account has been enabled for that payment method.

Digital wallets

Apple Pay and Google Pay cannot be offered as standalone buttons using this method. If you want to offer these as dedicated payment options at checkout, use the Wallet SDK.

Implementation

Include the institutionId field in your payment request. When a valid institutionId is included, the customer is taken directly to that payment method. The institutionId for each payment method is available on the relevant Payment products page.

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

Customer Identity Verification

If your business operates in a high-risk industry, you are required to implement 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. before going live with Bank API payment methods.

To implement it in One API, pass the payer.identity object in the payment request:

{
  "siteCode": "YOUR_SITE_CODE",
  "amount": { "currency": "ZAR", "value": 100.00 },
  "merchantReference": "ORDER-001",
  "expireAt": "2026-12-31T23:59:59Z",
  "returnUrl": "https://yourstore.com/order-complete",
  "payer": {
    "id": "CUSTOMER-123",
    "name": "Firstname Lastname",
    "identity": {
      "type": "said",
      "country": "ZA",
      "identifier": "0000000000000"
    }
  }
}

Identity fields

Field Type Description
payer.identity.type string said for South African ID, passport for foreign passport
payer.identity.country string 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 country code, ZA for South Africa
payer.identity.identifier string The verified ID or passport number

For full details on Customer Identity Verification requirements see Customer Identity Verification.


Next steps

In the API reference

11 entries

Last updated