Quick start: accept your first payment
Accept your first Ozow payment with One API. Get a token, create a payment request, redirect the customer, and read the webhook that confirms it.
On this page7 sections
Build with AI 1 package
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.
This guide walks you through accepting your first payment using Ozow. By the end you'll have a working payment flow that creates a payment request and redirects your customer to complete their payment.
Note
If you're looking for a no-code or plugin option, head to Integration methods. This guide is for developers building an API integration.
Before you start
Make sure you have the following in place before continuing:
- An active Ozow merchant account
- Your Client ID and Client Secret from the One API Clients section of the Ozow Dashboard
- 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 the Dashboard
If you haven't completed these steps yet, see Prerequisites and onboarding first.
The scenario
The examples on this page use one ecommerce checkout:
Fynbos Supply Co. runs an online store. A customer has added a product to their cart and is ready to check out. The total order value is R100. Fynbos Supply Co. wants to 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. the customer to Ozow to complete the payment securely.
Step 1: Obtain an access token
One API 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 authentication. Before you can make any API calls you need to request an access token using your Client ID and Client Secret.
Send a POST request to the token endpoint:
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"
var client = new HttpClient();
var content = new FormUrlEncodedContent(
new[]
{
new KeyValuePair<string, string>("client_id", "YOUR_CLIENT_ID"),
new KeyValuePair<string, string>("client_secret", "YOUR_CLIENT_SECRET"),
new KeyValuePair<string, string>("scope", "payments"),
new KeyValuePair<string, string>("grant_type", "client_credentials"),
}
);
var response = await client.PostAsync("https://one.ozow.com/v1/token", content);
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://one.ozow.com/v1/token",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
"client_id" => "YOUR_CLIENT_ID",
"client_secret" => "YOUR_CLIENT_SECRET",
"scope" => "payments",
"grant_type" => "client_credentials",
]),
CURLOPT_HTTPHEADER => ["Content-Type: application/x-www-form-urlencoded"],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
const response = await fetch("https://one.ozow.com/v1/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: "YOUR_CLIENT_ID",
client_secret: "YOUR_CLIENT_SECRET",
scope: "payments",
grant_type: "client_credentials",
}),
});
const data = await response.json();
console.log(data);
import requests
response = requests.post(
"https://one.ozow.com/v1/token",
data={
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"scope": "payments",
"grant_type": "client_credentials",
},
)
print(response.json())
Successful response
{
"access_token": "eyJhbGciOiJSUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": "14400",
"scope": "payments"
}
Store the access_token, you'll need it in the next step. 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.
Step 2: Create a payment request
Now that you have an access token, create a payment request for your customer's R100 order.
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"
}'
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer",
"YOUR_ACCESS_TOKEN"
);
var payload = new
{
siteCode = "YOUR_SITE_CODE",
amount = new { currency = "ZAR", value = 100.00 },
merchantReference = "ORDER-001",
beneficiaryReference = "ONLINESHOP002",
expireAt = "2026-12-31T23:59:59Z",
returnUrl = "https://yourstore.com/order-complete",
};
var response = await client.PostAsync(
"https://one.ozow.com/v1/payments",
new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
);
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://one.ozow.com/v1/payments",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
"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",
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer YOUR_ACCESS_TOKEN",
"Content-Type: application/json",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
const response = await fetch("https://one.ozow.com/v1/payments", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
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",
}),
});
const data = await response.json();
console.log(data);
import requests
response = requests.post(
"https://one.ozow.com/v1/payments",
headers={
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
},
json={
"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",
},
)
print(response.json())
Successful response
{
"links": {
"self": "https://one.ozow.com/v1/payments/497f6eca-6276-4993-bfeb-53cbbbba6f08",
"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 your customer
Take the redirectUrl from the response and redirect your customer's browser to it. Ozow will
handle the payment experience from here, your customer selects their preferred payment method and
completes the payment on Ozow's secure hosted page.
By default, 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 on your payment page from the moment your account is active. Additional payment methods; such as Card, Buy Now Pay Later, Crypto, and 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; are enabled by the Ozow team on request. Once activated, they appear automatically on your payment page with no additional integration work required on your side.
Once the payment is complete, Ozow will redirect your customer back to the returnUrl you specified
in the payment request.
Important
Do not use the redirect response alone to confirm payment status. Always verify the outcome using 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. or by checking the transaction status via the API. See Step 4.
Important
returnUrl, notifyUrl and a webhook URL must all be reachable from the
internet. localhost is rejected with a 403, so a tunnel to your machine is what a local
integration needs rather than the address your browser uses.
Step 4: Handle the outcome
Ozow notifies you of the payment outcome in three ways:
Webhook notification: Ozow sends an HTTP POST to your designated webhook URL when the transaction completes. This is the recommended way to confirm payment status.
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
Every webhook notification includes Svix signature headers. Always 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 and a working verifier in four languages, with or without the Svix library. Retrieve your webhook secret with Get Webhook Secret.
Important
Never process a webhook without first verifying its signature. Do not rely on the redirect response alone to confirm payment status.
For full webhook implementation details see Redirect to Ozow.
notifyUrl: set notifyUrl on the payment request and Ozow also sends the standard Ozow
notification, with the same payload and the same hash the Payments API sends on a 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.. It is
optional and fires only for payments where you set it, so it is there for an integration that
already has a Payments API notification handler working. Point it at that handler and nothing about
it has to change.
Important
If you set notifyUrl and configure a webhook, both fire for the same
payment. Make your handlers 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.
Transaction status check: You can also query the transaction status directly via the API at any time:
GET https://one.ozow.com/v1/payments/{id}/transactions
Authorization: Bearer YOUR_ACCESS_TOKEN
A completed payment returns a transaction whose status is Successful. The full set is
Incomplete, Successful, Error, Pending and Refunded, which is the transaction's own status
rather than the four a webhook maps to; Transaction and settlement
statuses covers the difference. Compare the value without
case.
An id that matches no payment answers 200 with an empty result list, not a 404. Check whether
you got a transaction back rather than relying on the status code.
What's next?
You've accepted your first payment. Here's where to go from here:
- Set up webhooks: Redirect: One API covers webhook setup and verification in full
- Handle edge cases: learn how to handle failed payments, cancellations, and timeouts
- Explore other integration paths: Integration methods covers embedded, direct, and no-code options
- Go live: review the Building a secure integration checklist before switching to your production credentials
In the API reference
6 entries
- POST
/tokenGenerate Authentication Token One API - POST
/paymentsRequest Payment One API - GET
/payments/{id}/transactionsList Transactions for Payment One API - GET
/webhooksList Webhook Subscriptions One API - POST
/webhooksCreate Webhook Subscription One API - GET
/webhooks/{id}/secretGet Webhook Secret One API
Last updated