Refund a payment
Issue a refund on the Payments API, the legacy path. Refunds are merchant-initiated backend operations, funded from your float balance.
On this page15 sections
- Before you start
- Environments
- How refunds work
- Step 1: Get a bearer token
- Step 2: Generate the hash check
- Step 3: Submit the refund request
- Step 4: Handle the notification
- Refund statuses
- Refund resource status
- Notification statuses
- Querying refunds
- Get refund by ID
- Get refunds by transaction ID
- Get refunds by date and status
- Next steps
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.
- 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.
Legacy integration
The Payments API is a legacy integration path. For new integrations, use Refunds: One API instead. This guide is for merchants already using the Payments API.
This guide walks you through issuing refunds to your customers using the Payments API. Refunds are merchant-initiated backend operations, there is no customer-facing step. The entire flow happens in your backend.
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. required
Ozow uses your float balance to fund refunds. Make sure your float has sufficient funds before issuing refunds. See the Float top-up guide to load funds into your float.
Before you start
- You have your API key and 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 your Ozow Dashboard
- Your float balance is sufficient to cover the refund amounts
- You have the transaction IDs of the original payments you want to refund
- If you use
notifyUrlto receive refund status updates, it is publicly accessible via HTTPS
Environments
| Environment | Base URL | Dashboard |
|---|---|---|
| Production | https:/ |
dash.ozow.com |
| Staging | https:/ |
stagingdash.ozow.com |
How refunds work
The Payments API processes refunds in batches. Even when you are refunding a single transaction, you submit it as an array containing one refund request. Each refund item in the array requires its own hash check.
Step 1: Get a bearer token
Refund endpoints authenticate with 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 rather than the API key directly. Request one
before submitting refunds.
POST https://api.ozow.com/token
ApiKey: YOUR_API_KEY
Content-Type: application/x-www-form-urlencoded
curl -X POST "https://api.ozow.com/token" \
-H "ApiKey: <YOUR_API_KEY>" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=Password&SiteCode=YOUR_SITE_CODE"
var client = new HttpClient();
client.DefaultRequestHeaders.Add("ApiKey", "YOUR_API_KEY");
var content = new FormUrlEncodedContent(
new[]
{
new KeyValuePair<string, string>("grant_type", "Password"),
new KeyValuePair<string, string>("SiteCode", "YOUR_SITE_CODE"),
}
);
var response = await client.PostAsync("https://api.ozow.com/token", content);
var result = await response.Content.ReadAsStringAsync();
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ozow.com/token",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
"grant_type" => "Password",
"SiteCode" => "YOUR_SITE_CODE",
]),
CURLOPT_HTTPHEADER => [
"ApiKey: YOUR_API_KEY",
"Content-Type: application/x-www-form-urlencoded",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
const response = await fetch("https://api.ozow.com/token", {
method: "POST",
headers: {
"ApiKey": "YOUR_API_KEY",
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "Password",
SiteCode: "YOUR_SITE_CODE",
}),
});
const data = await response.json();
import requests
response = requests.post(
"https://api.ozow.com/token",
headers={"ApiKey": "YOUR_API_KEY"},
data={"grant_type": "Password", "SiteCode": "YOUR_SITE_CODE"},
)
data = response.json()
Successful response
{
"access_token": "YOUR_ACCESS_TOKEN",
"token_type": "Bearer",
"expires_in": "14400"
}
Store the access_token and request a new one before it expires. Include it in the Authorization
header of every refund request:
Authorization: Bearer YOUR_ACCESS_TOKEN
Step 2: Generate the hash check
Each refund item in the batch requires its own 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 check.
Critical, field order matters
Concatenate the fields in exactly the order shown below. Using the wrong order results in a hash check failure and the refund is rejected.
Hash field concatenation order
| Position | Field |
|---|---|
| 1 | transaction |
| 2 | amount, formatted with two decimal places, for example 100.00 |
| 3 | refund |
| 4 | notify |
Steps
- Concatenate the fields above in order
- Append your private key to the concatenated string
- Generate a SHA512 hash of the result and send the digest as lowercase hexadecimal
using System.Security.Cryptography;
using System.Text;
var transactionId = "00000000-0000-0000-0000-000000000000";
var amount = "50.00";
var refundReason = "Order cancellation";
var notifyUrl = "https://yourstore.com/notify";
var privateKey = "YOUR_PRIVATE_KEY";
var inputString = string.Concat(transactionId, amount, refundReason, notifyUrl, privateKey);
using SHA512 sha = SHA512.Create();
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(inputString));
var hashCheck = string.Concat(bytes.Select(b => b.ToString("x2")));
Console.WriteLine($"hashCheck: {hashCheck}");
<?php
$inputString =
$transactionId . $amount . $refundReason . $notifyUrl . $privateKey;
$hashCheck = hash("sha512", $inputString);
echo "hashCheck: " . $hashCheck;
?>
const crypto = require("crypto");
const inputString =
transactionId + amount + refundReason + notifyUrl + privateKey;
const hashCheck = crypto.createHash("sha512").update(inputString).digest("hex");
console.log("hashCheck:", hashCheck);
import hashlib
input_string = transaction_id + amount + refund_reason + notify_url + private_key
hash_check = hashlib.sha512(input_string.encode()).hexdigest()
print("hashCheck:", hash_check)
Step 3: Submit the refund request
POST https://api.ozow.com/secure/refunds/submit
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Content-Type: application/json
Submit every refund you want to process in a single request, as an array. Each item includes its own hash check from Step 2.
Request example
[
{
"transactionId": "00000000-0000-0000-0000-000000000000",
"amount": 50.00,
"refundReason": "Order cancellation",
"notifyUrl": "https://yourstore.com/notify",
"hashCheck": "YOUR_GENERATED_HASH"
},
{
"transactionId": "00000000-0000-0000-0000-000000000001",
"amount": 100.00,
"refundReason": "Duplicate charge",
"notifyUrl": "https://yourstore.com/notify",
"hashCheck": "YOUR_GENERATED_HASH"
}
]
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
transaction |
string | Yes | The Ozow transaction ID of the original payment |
amount |
number | Yes | Amount to refund. Must not exceed the original transaction amount |
refund |
string | No | Reason for the refund |
notify |
string | No | URL Ozow posts the refund notification to |
is |
boolean | No | Whether the refund is processed as an RTCReal-Time Clearing Payments that clear immediately rather than waiting for a batch. A batch run settles at set times through the day; a Real-Time Clearing payment moves the funds between the two bank accounts as it is made, so the recipient can rely on them straight away.PayInc refund. Defaults to false |
hash |
string | Yes | SHA512 hash generated in Step 2 |
For the full field reference see Submit refund.
Successful response
[
{
"refundId": "00000000-0000-0000-0000-000000000000",
"transactionId": "00000000-0000-0000-0000-000000000000",
"refundAmount": "50.00",
"errors": null
},
{
"refundId": null,
"transactionId": "00000000-0000-0000-0000-000000000001",
"refundAmount": "100.00",
"errors": ["Hash check invalid"]
}
]
Note
The API returns a response for each refund item in the array. Check the errors
field for each item, a null value means the refund was accepted. A non-null value means the
refund was rejected and includes the reason.
Store the refundId for each accepted refund, you can use it to check the status later.
Step 4: Handle the notification
Ozow sends a notification to your notifyUrl when a refund either completes or fails.
Notification fields
| Field | Description |
|---|---|
refund |
The refund identifier |
transaction |
The transaction identifier of the payment that was refunded |
currency |
The refund currency |
amount |
The refund amount |
status |
The refund status. See Notification statuses |
bank |
The name of the bank the refund was paid to |
account |
The masked account number the refund was paid to |
status |
Message about the refund status. Not always present |
is |
Whether RTC was used to pay the refund |
hash |
SHA512 hash used to verify the notification |
Verifying the notification hash
- Concatenate the fields in this order:
refundId,transactionId,currencyCode,amount,status,bankName,accountNumber,statusMessage, excludingisRtcandhash - Append your private key
- Lowercase the entire string
- Generate a SHA512 hash and compare it to the
hashfield received
Important
Always verify the notification hash before updating your records. Never update refund status without first verifying the hash.
Refund statuses
Two different endpoints represent refund status differently.
Refund resource status
Returned by getrefund, getrefunds, and getrefundsbytransactionid:
| Status | Description |
|---|---|
0 |
Pending, the refund request has been submitted and accepted |
1 |
Complete, the refund has been paid successfully |
2 |
Submitted, the refund has been assigned to a batch and is being processed |
3 |
Failed, the refund payment has failed |
4 |
Cancelled, the refund was cancelled before it was submitted |
5 |
Returned, the refund payment was returned because the destination account no longer exists |
Notification statuses
Sent in the status field of the notifyUrl notification:
| Status | Description |
|---|---|
Pending |
The refund request has been submitted and accepted |
Submitted |
The refund has been assigned to a batch and is being processed |
Complete |
The refund has been paid successfully |
Failed |
The refund payment has failed |
Cancelled |
The refund was cancelled before it was submitted |
Returned |
The refund payment was returned because the destination account no longer exists |
Pending |
The refund is being investigated |
Invalid |
The refund notification could not be validated |
Error |
The refund could not be processed due to an error |
Querying refunds
Get refund by ID
GET https://api.ozow.com/secure/refunds/getrefund?refundId={refundId}
Authorization: Bearer YOUR_ACCESS_TOKEN
Get refunds by transaction ID
Retrieve every refund issued against a specific original payment transaction:
GET https://api.ozow.com/secure/refunds/getrefundsbytransactionid?transactionId={transactionId}
Authorization: Bearer YOUR_ACCESS_TOKEN
Get refunds by date and status
GET https://api.ozow.com/secure/refunds/getrefunds?refundDate={refundDate}&status={status}
Authorization: Bearer YOUR_ACCESS_TOKEN
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
refund |
string | Yes | Date of refunds to return. See Get refunds for the exact accepted format |
status |
string | Yes | Refund status code to filter by, from the Refund resource status table |
Next steps
- Review the Building a secure integration checklist
- See the Payments API reference for the full refund endpoint specifications
- Need to issue refunds without writing code? See Refunds: No-code
- Considering migrating to One API? See Refunds: One API
In the API reference
6 entries
- POST
/tokenGet API token Payments API - POST
/secure/refunds/submitSubmit refund Payments API - GET
/secure/refunds/getrefundGet refund Payments API - GET
/secure/refunds/getrefundsbytransactionidGet refunds by transaction ID Payments API - GET
/secure/refunds/getrefundsGet refunds Payments API - POST Ozow sends your notification URLRefund notification Payments API
Last updated