# Embed checkout in your own page

> Everything needed to keep the customer on your site while they pay, as an iframe, a modal, or the Wallet SDK for Apple Pay and Google Pay, with the notification that actually confirms the payment.

Three ways to take a payment without sending the customer to a page of Ozow's:
the payment page in an iframe on your checkout, the same page in a modal over
it, or the Wallet SDK rendering Apple Pay, Google Pay and optionally card into a
container you place. They are alternatives, not steps. Read the comparison, then
implement one.

**All three are on the Payments API, not One API.** Authentication and the
notification are the Payments API's throughout: an API key and a hash, not a
bearer token and a Svix signature. Create the payment request server-side with
`POST /postpaymentrequest`, which returns `paymentRequestId`; the SDK takes that
value as its `requestId` option. For a redirect payin, take `take-a-payment`
instead. Mixing the two contracts in one integration means two sets of
credentials and two notification formats.

**The SDK's events are not confirmation.** The outcome arrives as a notification
to your `NotifyUrl`, hash verified. A `postMessage` event or a redirect to your
success URL reports what the browser did, not what the bank did.

The hash is computed from your private key, so the payment request must be built
server-side in every case. Browser code that builds it has published the key.

## What this was built from

- Ozow Hub, commit `e0b2a572`
- `payments-api` version 1.0, OpenAPI document: https://hub.ozow.com/api-reference/specs/payments-api.yaml
- Build against `https://api.ozow.com` for `payments-api`
- 9 pages, 3 operations, inlined in full below
- The same package as links: https://hub.ozow.com/bundles/embed-checkout-in-your-page.md

---

# Implement against these

Every field name, order and format below is exact. Copy them as written.

---

# Embedded iframe

> Load the Ozow payment page inside a container on your own checkout with the Ozow SDK, so your customer never leaves your site.

Source: https://hub.ozow.com/integration-methods/apis/payin/embedded-iframe/

The embedded iframe embeds the Ozow payment page directly inside a container on your own page. Your
customer never leaves your site, the full payment experience loads inside an iframe within your
checkout flow.

**This guide is a Payments API integration.** You create the payment request with
[`POST /postpaymentrequest`](https://hub.ozow.com/api-reference/payments-api/post-post-payment-request.md), authenticate with
your API key, and sign the hash with your private key: the One API Client ID and Client Secret
are not used here. [Prerequisites and
onboarding](https://hub.ozow.com/getting-started/prerequisites-and-onboarding.md) says which credential
belongs to which API.

> ⚠️ **Important**: You must use the Ozow SDK to implement the iframe checkout. Do not attempt to
> build your own iframe implementation or embed the Ozow payment page directly without the SDK. Only
> SDK-based implementations are supported and guaranteed to function correctly.

Not sure which embedded option is right for you? See [Choose your integration](https://hub.ozow.com/integration-methods.md).

## Before you start

- You have completed [Prerequisites and onboarding](https://hub.ozow.com/getting-started/prerequisites-and-onboarding.md)
- You have your API key, private key, and site code from your [Ozow Dashboard](https://dash.ozow.com)
- **jQuery 1.12.x or higher is required**: the Ozow SDK depends on jQuery. Make sure it is loaded on
  your page before the Ozow SDK script.
- Your notification URL, success URL, cancel URL, and error URL are set up and publicly accessible
  via HTTPS

## How iframe checkout works

```mermaid
sequenceDiagram
    participant C as Customer
    participant M as Your page
    participant S as Ozow SDK
    participant O as Payments API
    participant P as Ozow iframe

    C->>M: Reaches checkout
    M->>O: POST /postpaymentrequest
    O-->>M: Returns payment URL
    M->>S: createPaymentFrame(container, paymentUrl, postData)
    S->>P: Loads Ozow payment page in iframe
    C->>P: Completes payment inside iframe
    O-->>M: Sends notification to NotifyUrl
    M->>M: Verifies notification hash
    S->>M: Redirects parent page after completion
```

## Environments

| Environment | Payment request endpoint | SDK payment URL | Dashboard |
|---|---|---|---|
| Production | `https://api.ozow.com/postpaymentrequest` | `https://pay.ozow.com/` | [dash.ozow.com](https://dash.ozow.com) |
| Staging | `https://stagingapi.ozow.com/postpaymentrequest` | `https://stagingpay.ozow.com/` | [stagingdash.ozow.com](https://stagingdash.ozow.com) |

> ℹ️ **Note**: The SDK payment URL (`pay.ozow.com`) is different from the API endpoint
> (`api.ozow.com`). Make sure you use the correct URL for each purpose.

---

## Core integration

### Step 1: Create a payment request

Create a payment request server-side using the Payments API. This is identical to the redirect
integration, follow [Steps 1 and 2 in the Redirect: Payments API
guide](https://hub.ozow.com/integration-methods/apis/deprecated-integrations/redirect-to-ozow.md) to generate the hash check and post the payment
request.

The response returns a `url`, this is your `paymentUrl` for the SDK in Step 4.

> ⚠️ **Important**: Never generate the hash or expose your private key in browser code. The payment
> request must be created server-side.

---

### Step 2: Install the SDK

Add jQuery and the Ozow SDK script to your page. jQuery must be loaded before the Ozow SDK.

```html
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://static-content.ozow.com/scripts/js/v2/ozow-integration-2.0.min.js"></script>
```

---

### Step 3: Add the page markup

Add a container div where the iframe will be rendered, a trigger element to launch the payment, and
a cancel button:

```html
<!-- Container where the iframe will be rendered -->
<div id="paymentContainer"></div>

<!-- Trigger element - customer clicks this to start payment -->
<input type="radio" id="payUsingOzow" value="Ozow" /> Pay with Ozow

<!-- Cancel button -->
<button id="cancelOzowPayment">Cancel Payment</button>
```

---

### Step 4: Initialise the SDK and launch the iframe

Instantiate the SDK and wire up your trigger element to launch the iframe when the customer selects
Ozow as their payment method.

```javascript
const ozow = new Ozow();

const paymentUrl = "https://pay.ozow.com/"; // Use https://stagingpay.ozow.com/ for staging
const postData = {
  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",
};

document.getElementById("payUsingOzow").onclick = () => {
  ozow.createPaymentFrame("paymentContainer", paymentUrl, postData);
};
```

The SDK automatically appends `?viewName=JsInjection` to the payment URL. Your success, cancel, and
error URLs are automatically rewritten to `/payment/iframeredirect?redirecturl=<ENCODED_URL>`; no
extra work required on your side.

---

### Step 5: Handle cancellation

Wire up the cancel button to dismiss the iframe:

```javascript
document.getElementById("cancelOzowPayment").onclick = () => {
  ozow.cancelFramePayment();
};
```

---

### Step 6: Handle the payment outcome

Ozow sends a notification to your `NotifyUrl` when the transaction completes. Handle this exactly as
described in [Step 4: Handle the notification
response](https://hub.ozow.com/integration-methods/apis/deprecated-integrations/redirect-to-ozow.md#step-4-handle-the-notification-response) in
the Redirect, Payments API guide, same format, same hash verification process.

After the payment is complete, the SDK automatically redirects the parent page to your `SuccessUrl`,
`CancelUrl`, or `ErrorUrl` depending on the outcome.

> ℹ️ **Note**: The SDK handles the following postMessage events internally. You only need to add a
> custom listener if you require additional behaviour beyond the defaults.

| Event | SDK behaviour |
|---|---|
| `ozowResize` | Resizes the iframe height automatically |
| `ipayMessage` | Redirects the parent page after payment completion |

**Optional custom event listener**

```javascript
window.addEventListener("message", (e) => {
  if (e.data?.event === "ozowResize") {
    // Optional custom handling
  }
});
```

---

## Error handling

If the container ID, payment URL, or post data are invalid, the SDK will:

1. Log a descriptive error message to the browser console
2. Display an alert to the customer: **"Payment could not be completed, please contact the site administrator."**

Check the browser console for detailed error information during development.

---

## Complete example

```html
<div id="paymentContainer"></div>
<input type="radio" id="payUsingOzow" value="Ozow" /> Pay with Ozow
<button id="cancelOzowPayment">Cancel</button>

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://static-content.ozow.com/scripts/js/v2/ozow-integration-2.0.min.js"></script>
<script>
  const ozow = new Ozow();
  const paymentUrl = "https://pay.ozow.com/";
  const postData = {
    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",
  };

  document.getElementById("payUsingOzow").onclick = () =>
    ozow.createPaymentFrame("paymentContainer", paymentUrl, postData);

  document.getElementById("cancelOzowPayment").onclick = () =>
    ozow.cancelFramePayment();
</script>
```

---

# Embedded modal

> Open the Ozow payment page as an overlay on your own checkout with the Ozow SDK, so your customer pays without leaving the page.

Source: https://hub.ozow.com/integration-methods/apis/payin/embedded-modal/

The embedded modal loads the Ozow payment page in an overlay on top of your own page. Your customer
never leaves your site, the payment experience appears as a modal dialog while your checkout page
remains visible in the background.

**This guide is a Payments API integration.** You create the payment request with
[`POST /postpaymentrequest`](https://hub.ozow.com/api-reference/payments-api/post-post-payment-request.md), authenticate with
your API key, and sign the hash with your private key: the One API Client ID and Client Secret
are not used here. [Prerequisites and
onboarding](https://hub.ozow.com/getting-started/prerequisites-and-onboarding.md) says which credential
belongs to which API.

> ⚠️ **Important**: You must use the Ozow SDK to implement the modal checkout. Do not attempt to
> build your own modal implementation or embed the Ozow payment page directly without the SDK. Only
> SDK-based implementations are supported and guaranteed to function correctly.

Not sure which embedded option is right for you? See [Choose your integration](https://hub.ozow.com/integration-methods.md).

## Before you start

- You have completed [Prerequisites and onboarding](https://hub.ozow.com/getting-started/prerequisites-and-onboarding.md)
- You have your API key, private key, and site code from your [Ozow Dashboard](https://dash.ozow.com)
- **jQuery 1.12.x or higher is required**: the Ozow SDK depends on jQuery. Make sure it is loaded on
  your page before the Ozow SDK script.
- Your notification URL, success URL, cancel URL, and error URL are set up and publicly accessible
  via HTTPS

## How modal checkout works

The modal checkout works identically to the iframe checkout with two differences:

1. No container div is required, the modal iframe is injected automatically by the SDK
2. Use `createPaymentModal()` instead of `createPaymentFrame()`

```mermaid
sequenceDiagram
    participant C as Customer
    participant M as Your page
    participant S as Ozow SDK
    participant O as Payments API
    participant P as Ozow modal iframe

    C->>M: Reaches checkout
    M->>O: POST /postpaymentrequest
    O-->>M: Returns payment URL
    M->>S: createPaymentModal(paymentUrl, postData)
    S->>P: Injects modal and loads Ozow payment page
    C->>P: Completes payment inside modal
    O-->>M: Sends notification to NotifyUrl
    M->>M: Verifies notification hash
    S->>M: Redirects parent page after completion
```

## Environments

| Environment | Payment request endpoint | SDK payment URL | Dashboard |
|---|---|---|---|
| Production | `https://api.ozow.com/postpaymentrequest` | `https://pay.ozow.com/` | [dash.ozow.com](https://dash.ozow.com) |
| Staging | `https://stagingapi.ozow.com/postpaymentrequest` | `https://stagingpay.ozow.com/` | [stagingdash.ozow.com](https://stagingdash.ozow.com) |

---

## Core integration

### Step 1: Create a payment request

Create a payment request server-side using the Payments API. This is identical to the redirect
integration, follow [Steps 1 and 2 in the Redirect: Payments API
guide](https://hub.ozow.com/integration-methods/apis/deprecated-integrations/redirect-to-ozow.md) to generate the hash check and post the payment
request.

The response returns a `url`, this is your `paymentUrl` for the SDK in Step 4.

> ⚠️ **Important**: Never generate the hash or expose your private key in browser code. The payment
> request must be created server-side.

---

### Step 2: Install the SDK

Add jQuery and the Ozow SDK script to your page. jQuery must be loaded before the Ozow SDK.

```html
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://static-content.ozow.com/scripts/js/v2/ozow-integration-2.0.min.js"></script>
```

---

### Step 3: Add the page markup

The modal checkout only requires two UI elements, a trigger button and a cancel button. No container
div is needed.

```html
<!-- Trigger element - customer clicks this to open the modal -->
<button id="payUsingOzow">Pay with Ozow</button>

<!-- Cancel button -->
<button id="cancelOzowPayment">Cancel</button>
```

---

### Step 4: Initialise the SDK and launch the modal

Instantiate the SDK and wire up your trigger element to launch the modal when the customer selects
Ozow as their payment method.

```javascript
const ozow = new Ozow();

const paymentUrl = "https://pay.ozow.com/"; // Use https://stagingpay.ozow.com/ for staging
const postData = {
  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",
};

document.getElementById("payUsingOzow").onclick = () => {
  ozow.createPaymentModal(paymentUrl, postData);
};
```

The SDK automatically appends `?viewName=JsPopup` to the payment URL. Your success, cancel, and
error URLs are automatically rewritten to `/payment/iframeredirect?redirecturl=<ENCODED_URL>`; no
extra work required on your side.

---

### Step 5: Handle cancellation

Wire up the cancel button to dismiss the modal:

```javascript
document.getElementById("cancelOzowPayment").onclick = () => {
  ozow.cancelFramePayment();
};
```

---

### Step 6: Handle the payment outcome

Ozow sends a notification to your `NotifyUrl` when the transaction completes. Handle this exactly as
described in [Step 4: Handle the notification
response](https://hub.ozow.com/integration-methods/apis/deprecated-integrations/redirect-to-ozow.md#step-4-handle-the-notification-response) in
the Redirect, Payments API guide, same format, same hash verification process.

After payment completes, the SDK automatically redirects the parent page to your `SuccessUrl`,
`CancelUrl`, or `ErrorUrl` depending on the outcome.

> ℹ️ **Note**: The SDK handles the following postMessage events internally. You only need to add a
> custom listener if you require additional behaviour beyond the defaults.

| Event | SDK behaviour |
|---|---|
| `ozowShowModal` | Opens the modal overlay |
| `ozowHideModal` | Closes the modal overlay |
| `ozowResize` | Resizes the iframe height automatically |
| `ipayMessage` | Redirects the parent page after payment completion |

**Optional custom event listener**

```javascript
window.addEventListener("message", (e) => {
  if (e.data?.event === "ozowHideModal") {
    // e.g. show a "Payment closed" notification
  }
});
```

---

## Error handling

If the payment URL or post data are invalid, the SDK will:

1. Log a descriptive error message to the browser console
2. Display an alert to the customer: **"Payment could not be completed, please contact the site administrator."**

Check the browser console for detailed error information during development.

---

## Complete example

```html
<button id="payUsingOzow">Pay with Ozow</button>
<button id="cancelOzowPayment">Cancel</button>

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://static-content.ozow.com/scripts/js/v2/ozow-integration-2.0.min.js"></script>
<script>
  const ozow = new Ozow();
  const paymentUrl = "https://pay.ozow.com/";
  const postData = {
    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",
  };

  document.getElementById("payUsingOzow").onclick = () =>
    ozow.createPaymentModal(paymentUrl, postData);

  document.getElementById("cancelOzowPayment").onclick = () =>
    ozow.cancelFramePayment();
</script>
```

---

## Next steps

- Review the [Building a secure
  integration](https://hub.ozow.com/getting-started/building-a-secure-integration.md) checklist before going
  live
- Test your integration using [Payin test cases](https://hub.ozow.com/integration-methods/apis/deprecated-integrations/payin-test-cases-payments-api.md)
- See the [Payments API reference](https://hub.ozow.com/api-reference/payments-api.md) for the full technical specification
- Looking for digital wallets or card? See the [Wallet SDK](https://hub.ozow.com/integration-methods/apis/payin/embedded-wallet.md)

---

# Embedded wallet

> Offer Apple Pay, Google Pay and cards on your own checkout through a secure Ozow iframe, so card data never touches your page. Approved merchants only.

Source: https://hub.ozow.com/integration-methods/apis/payin/embedded-wallet/

> ⚠️ **Limited availability**: Embedded wallets are available to approved merchants only. Contact
> your account manager or [support@ozow.com](mailto:support@ozow.com) to enquire about eligibility
> and onboarding.

**This guide is a Payments API integration.** You create the payment request with
[`POST /postpaymentrequest`](https://hub.ozow.com/api-reference/payments-api/post-post-payment-request.md), authenticate with
your API key, and sign the hash with your private key: the One API Client ID and Client Secret
are not used here. [Prerequisites and
onboarding](https://hub.ozow.com/getting-started/prerequisites-and-onboarding.md) says which credential
belongs to which API.

## What is an embedded wallet?

The embedded wallet lets you offer digital wallets, Apple Pay and Google Pay, and optionally card
payments, directly on your checkout page. The payment UI renders inside a secure Ozow iframe on your
domain. Your customers never leave your site and sensitive payment data never touches your page.

> ℹ️ **This is the only way to offer Apple Pay and Google Pay as standalone payment options.**
> Digital wallets cannot be triggered via the standalone button method used for other payment
> methods. If you want to offer Apple Pay or Google Pay at checkout, you must use the Wallet SDK.

Not sure which embedded option is right for you? See [Overview: how to choose](https://hub.ozow.com/integration-methods.md).

## Before you start

Before integrating, confirm the following with Ozow:

- **Wallet enablement**: Apple Pay, Google Pay, and/or card must be enabled on your merchant profile
  by Ozow before they will appear
- **Origin allowlist**: every checkout origin (scheme + host + port) where you will load the SDK
  must be registered with Ozow. Requests from unlisted origins will receive `403 Forbidden`
- **Apple Pay domain verification**: if you are offering Apple Pay, each top-level checkout domain
  must be verified (see [Apple Pay setup](#apple-pay-setup) below)
- **Settlement currency**: the amount and currency in your payment request must match your Ozow
  settlement configuration

You will also need:

- An active Ozow merchant account with the relevant payment methods enabled
- A server-side integration to create payment requests
- HTTPS on your checkout domain with a valid TLS certificate

## How the Wallet SDK works

```mermaid
sequenceDiagram
    participant C as Customer
    participant M as Your checkout page
    participant S as Wallet SDK
    participant O as Ozow API
    participant I as Ozow iframe

    C->>M: Reaches checkout
    M->>O: Create payment request (server-side)
    O-->>M: Returns requestId
    M->>S: Ozow.init({ requestId }).mount('#container')
    S->>I: Loads wallet buttons inside secure iframe
    C->>I: Taps wallet button or enters card details
    I->>O: Encrypts and submits payment
    O-->>S: Fires authorizationResult event
    S-->>M: Merchant handles outcome
```

## Environments

| Environment | API base URL | iframe origin | Dashboard |
|---|---|---|---|
| Production | `https://pay.ozow.com` | `https://pay.ozow.com` | [dash.ozow.com](https://dash.ozow.com) |
| Staging | `https://stagingpay.ozow.com` | `https://stagingpay.ozow.com` | [stagingdash.ozow.com](https://stagingdash.ozow.com) |

---

## Core integration

### Step 1: Create a payment request (server-side)

Your server creates the payment request with
[`POST /postpaymentrequest`](https://hub.ozow.com/api-reference/payments-api/post-post-payment-request.md). The response returns
`paymentRequestId`, a GUID, which you pass to the SDK as its `requestId` option.

> ⚠️ **Important**: The payment request must be created server-side. Never build the hash, expose
> your private key, or call the payment request endpoint from browser code.

Pass the `requestId` to your checkout page, typically embedded in the page HTML, returned from your
own API, or passed as a query parameter.

> ℹ️ **One request per checkout attempt**: pass a fresh `requestId` for each new payment session. If
> a payment fails and the customer retries, call `destroy()` and create a new payment request
> server-side before reinitialising the SDK.

---

### Step 2: Load the SDK script

Load the SDK from Ozow's CDN. Contact Ozow for the current recommended version and matching SRI hash
for your environment.

```html
<script
  src="https://cdn.ozow.com/sdk/v1/1.7.0/ozow.js"
  integrity="sha384-YOUR_SRI_HASH"
  crossorigin="anonymous"
></script>
```

> ℹ️ **Pinned version**: the SDK URL includes a specific version number. There is no `latest`
> channel, version bumps are an explicit change on your side. This ensures predictable behaviour and
> no silent CDN drift.

> ⚠️ **Single script tag only**: include the SDK script once per page. It exposes a global
> `window.Ozow` object.

---

### Step 3: Add the mount container

Add a container element where the SDK will render the wallet buttons:

```html
<div id="ozow-payment"></div>
```

No additional markup is required. The SDK injects the iframe automatically.

---

### Step 4: Initialise and mount the SDK

```javascript
const requestId = "YOUR_REQUEST_ID"; // From your server

const ozow = Ozow.init({
  requestId: requestId,
  environment: "production", // 'test' | 'production'
  apiBaseUrl: "https://pay.ozow.com",
  iframeBaseUrl: "https://pay.ozow.com",
});

// Listen for events before mounting
ozow.on("ready", (payload) => {
  console.log("SDK ready", payload.availableWallets);
  if (payload.availableWallets.length === 0) {
    // No wallets available on this device - show fallback payment methods
    showFallbackPaymentMethods();
  }
});

ozow.on("authorizationResult", (payload) => {
  switch (payload.status) {
    case "approved":
      // Payment authorised - fulfil order using payload.transactionId
      completeOrder(payload.transactionId);
      break;
    case "cancelled":
      // Customer cancelled - re-enable checkout
      unlockCheckout();
      break;
    case "declined":
    case "error":
    default:
      // Payment failed - show message and allow retry
      showPaymentFailed(payload.reason);
      break;
  }
});

ozow.on("error", (payload) => {
  console.error(payload.error.code, payload.error.message);
  showPaymentFailed(payload.error.message);
});

// Mount the SDK
ozow.mount("#ozow-payment", {
  wallets: ["applePay", "googlePay"],
  // To include card when enabled on your merchant profile:
  // paymentMethods: ['card', 'applePay', 'googlePay'],
});
```

> ⚠️ **Amount lock**: once the customer opens a wallet sheet, the payment amount must not change.
> Listen for the `walletSheetOpened` event and lock your cart at that point.

---

### Step 5: Handle lifecycle events

Subscribe to events using `ozow.on(eventName, handler)` and unsubscribe using `ozow.off`.

**Event catalogue**

| Event | When it fires | Recommended action |
|---|---|---|
| `ready` | iframe loaded and wallet availability known | Enable checkout UI; check `availableWallets` |
| `availability` | Per-provider availability resolved | Telemetry; explain why a button is hidden |
| `walletSheetOpened` | Customer tapped a wallet button | Lock cart amount |
| `walletTokenIssued` | Wallet authorised; token submitted internally | Show "Processing..." indicator |
| `authorizationResult` | Terminal payment outcome | Show the outcome. Fulfil on the notification, not on this |
| `walletSheetClosed` | Wallet sheet dismissed | Re-enable UI if not a terminal outcome |
| `error` | Recoverable or fatal SDK error | Show error message; check `error.code` |

> ℹ️ **Late subscribers**: the SDK replays `ready` and terminal `error` events to handlers
> registered after `mount`. You can safely attach listeners immediately after `init` or defer until
> your SPA hydrates.

**`authorizationResult` is not the confirmation.**

The SDK reaches that outcome by polling from the browser, and it gives up after 60 seconds. It tells
you what the customer's browser saw, which is not the same as what the bank did, and a customer who
closes the tab produces no event at all.

The payment is confirmed the same way every other Ozow payin is: by the notification Ozow sends to
your server. Because this guide creates the payment with
[`POST /postpaymentrequest`](https://hub.ozow.com/api-reference/payments-api/post-post-payment-request.md), that is the Payments API
[transaction notification](https://hub.ozow.com/api-reference/payments-api/webhooks/transaction-notification.md), posted to the
`NotifyUrl` on the request and verified by its `Hash`. Handle it exactly as [Step 4: Handle the
notification response](https://hub.ozow.com/integration-methods/apis/deprecated-integrations/redirect-to-ozow.md#step-4-handle-the-notification-response)
describes: same format, same hash check.

> ⚠️ **Important**: Update the order from the notification. Use `authorizationResult` to move the
> customer's screen on, and nothing else.

**Recommended event handler pattern**

```javascript
ozow.on("walletSheetOpened", () => {
  lockCartAmount();
});

ozow.on("walletTokenIssued", () => {
  showProcessingOverlay();
});

ozow.on("authorizationResult", ({ status, transactionId, reason }) => {
  hideProcessingOverlay();
  switch (status) {
    case "approved":
      return completeOrder(transactionId);
    case "cancelled":
      return unlockCheckout();
    default:
      return showPaymentFailed(reason);
  }
});

ozow.on("error", ({ error }) => {
  hideProcessingOverlay();
  showPaymentFailed(error.message);
});
```

---

### Step 6: Handle retries and cleanup

Call `destroy()` whenever the customer leaves checkout or retries a failed payment. Always create a
new payment request server-side before reinitialising the SDK with a new `requestId`.

```javascript
// Tear down listeners, iframe, and in-flight work
ozow.destroy();

// For a retry - create a new payment request server-side first
const retry = Ozow.init({
  requestId: newRequestId,
  apiBaseUrl: "https://pay.ozow.com",
  iframeBaseUrl: "https://pay.ozow.com",
});
retry.mount("#ozow-payment", { wallets: ["applePay", "googlePay"] });
```

| Scenario | Action |
|---|---|
| Payment failed / customer retry | `destroy()`, create new payment request server-side, reinitialise with new `requestId` |
| SPA route change away from checkout | Call `destroy()` in your route teardown |
| Remounting on same page | Call `destroy()` before remounting, only one active instance per page |

---

## Apple Pay setup

Apple Pay in the Wallet SDK validates your top-level checkout domain, not the Ozow iframe origin.
Ozow handles certificates and server-side merchant validation. You are responsible for domain
verification.

**What you must do**

| Step | Detail |
|---|---|
| Register each checkout domain with Ozow | Include production, staging, and localhost origins |
| Host the domain association file | Ozow supplies the file. Serve it at exactly: `https://YOUR_DOMAIN/.well-known/apple-developer-merchantid-domain-association` |
| No redirects on the well-known path | Apple must fetch the file directly, no 301/302 redirects |
| HTTPS only | Valid TLS certificate required on your checkout domain |
| Update your CSP | Add `https://applepay.cdn-apple.com` to `script-src` and `connect-src` |
| Do not load Apple Pay JS yourself | The SDK lazy-loads Apple's script: do not add a duplicate script tag |
| Do not mock `window.ApplePaySession` | Test doubles that replace the constructor break the payment sheet in all browsers |

**Cross-browser behaviour**

| Browser | Behaviour |
|---|---|
| Safari (macOS / iOS) | Native Apple Pay sheet |
| Chrome, Edge, Firefox (including Windows) | Apple Pay button opens a QR code sheet; customer completes on an iOS 18+ device |
| Unsupported browsers | Button hidden after availability check |

**Common Apple Pay failures**

| Symptom | Likely cause |
|---|---|
| Apple Pay button never appears | Domain not verified, merchant not Apple-enabled, or device unavailable |
| `apple_pay_validation_failed` | Domain not on Ozow allowlist, or association file missing or misconfigured |
| `wallet_sheet_failed` | Another script overwrote `window.ApplePaySession` before Apple's SDK loaded |

---

## Google Pay setup

No Google-side merchant onboarding is required per checkout domain. Google validates the Ozow iframe
origin (`pay.ozow.com`), which is already approved by Google.

You only need to:

- Enable Google Pay on your Ozow merchant profile
- Ensure your CSP allows the Ozow iframe origin (see [Security and CSP](#security-and-csp) below)

---

## Card payments

When card is enabled on your merchant profile, the Wallet SDK can show a card payment form alongside
wallet buttons. Card fields render only inside the Ozow iframe; card data never enters your DOM,
keeping your checkout page out of PCI cardholder data scope. 3DS is handled entirely inside the Ozow
iframe.

To include card, pass it in `paymentMethods` when mounting:

```javascript
ozow.mount("#ozow-payment", {
  paymentMethods: ["card", "applePay", "googlePay"],
});
```

---

## Security and CSP

Add these directives to your checkout page Content Security Policy:

```http
Content-Security-Policy: script-src 'self' https://cdn.ozow.com https://applepay.cdn-apple.com; frame-src 'self' https://pay.ozow.com;
```

Adjust the hostnames for staging (`stagingpay.ozow.com`).

> ℹ️ **iframe attributes**: the SDK creates `<iframe allow="payment">` automatically. Do not strip
> payment permissions on the mount container.

---

## Error codes

| Code | Typical cause |
|---|---|
| `request_invalid` | Missing or malformed `requestId` |
| `request_load_failed` | Payment request not found or API unreachable |
| `mount_failed` | Mount operation failed |
| `invalid_mount_target` | Container selector not found |
| `not_initialized` | `mount` called before `init` |
| `invalid_config` | Unsupported configuration |
| `apple_pay_unavailable` | Device or browser cannot offer Apple Pay |
| `apple_pay_validation_failed` | Domain verification or merchant validation failed |
| `apple_pay_submit_failed` | Apple Pay token submission failed |
| `google_pay_unavailable` | Google Pay not ready on device |
| `google_pay_submit_failed` | Google Pay submission failed |
| `transaction_initiate_failed` | Could not create transaction |
| `polling_failed` | Polling timed out or errored |
| `authorization_failed` | Backend declined or errored |
| `three_ds_failed` | 3DS step-up failed |
| `wallet_sheet_failed` | Wallet sheet could not open |
| `network_error` | Network request failed |
| `destroyed` | Operation called after `destroy()` |
| `internal_error` | Unexpected SDK error |

---

## Go-live checklist

Use this checklist before going live.

**Backend**

- [ ] Payment request creation is server-side only, private key never in browser code
- [ ] Checkout page receives a valid `requestId` per payment attempt
- [ ] Order fulfilment is keyed on `transactionId` from `authorizationResult`

**Frontend**

- [ ] Pinned CDN URL with SRI hash on the script tag
- [ ] `Ozow.init` and `mount` use the correct environment URLs
- [ ] Handlers registered for `ready`, `authorizationResult`, and `error`
- [ ] `destroy()` called on checkout exit and before retry
- [ ] Cart amount locked after wallet sheet opens

**Apple Pay**

- [ ] Every checkout domain hosts `.well-known/apple-developer-merchantid-domain-association`
- [ ] Domain registered with Ozow and verified with Apple
- [ ] CSP allows `applepay.cdn-apple.com`
- [ ] No duplicate Apple Pay script or `ApplePaySession` mock on page

**Security**

- [ ] Checkout origin(s) registered on Ozow merchant allowlist
- [ ] CSP allows Ozow CDN and pay iframe origin(s)
- [ ] HTTPS on checkout domain

**Card (if applicable)**

- [ ] Card enabled on merchant profile
- [ ] `paymentMethods` includes `'card'` when mounting

---

## Next steps

- Review the [Building a secure
  integration](https://hub.ozow.com/getting-started/building-a-secure-integration.md) checklist before going
  live
- Contact [support@ozow.com](mailto:support@ozow.com) to enable wallet and card payment methods on
  your merchant profile
- Contact Ozow to obtain the current SDK version and SRI hash for your environment
- See the [Payments API reference](https://hub.ozow.com/api-reference/payments-api.md) for the full technical specification

---

# Payin test cases

> The payments to run before you go live on the Payments API, what each one delivers, and a notification handler that survives all of them.

Source: https://hub.ozow.com/integration-methods/apis/deprecated-integrations/payin-test-cases-payments-api/

> ⚠️ **The Payments API is deprecated.** This guide applies to you if your integration posts to
> `api.ozow.com` and builds a SHA512 hash. Your integration keeps working and remains supported. New
> payment methods and features are released on the One API only, and all new integrations use it,
> no new merchants are onboarded onto the Payments API. No end-of-life date has been set. We
> recommend planning a move when you next have development capacity. See [Migrating to One
> API](https://hub.ozow.com/integration-methods/apis/deprecated-integrations/migrating-to-one-api.md).

These test cases are recommended but not mandatory for go-live. Run at least the core ones before
you accept real payments. The optional ones apply only if you have built the feature they test.

They cover the Payments API, which is what the [embedded iframe](https://hub.ozow.com/integration-methods/apis/payin/embedded-iframe.md),
[embedded modal](https://hub.ozow.com/integration-methods/apis/payin/embedded-modal.md) and [embedded wallet](https://hub.ozow.com/integration-methods/apis/payin/embedded-wallet.md)
checkouts are built on as well as the legacy redirect. Building on One API? Use [payin test
cases](https://hub.ozow.com/integration-methods/testing/payin-test-cases-one-api.md); the statuses and the field names are different.

> ℹ️ **Testing environment**: Payin integrations can be tested directly in production. Any test
> transactions settle into your configured bank account. To test without moving real money, staging
> credentials are available on request: contact your account manager or
> [support@ozow.com](mailto:support@ozow.com).

## What arrives, and what it says

A Payments API payment tells you its outcome on the
[transaction notification](https://hub.ozow.com/api-reference/payments-api/webhooks/transaction-notification.md): a form encoded
`POST` to the `NotifyUrl` on the payment request, authenticated by its `Hash` field. Set no
`NotifyUrl` and nothing is delivered.

`Status` is the transaction's own status, not a mapped one: `Complete`, `Cancelled`, `Error`,
`Abandoned`, `PendingInvestigation` and the rest of the set on the [statuses
page](https://hub.ozow.com/integration-methods/statuses.md).

> ⚠️ **Important**: The notification is an unauthenticated `POST` to a URL anyone can call. The
> `Hash` is what makes it Ozow's. A handler that trusts the body without checking it can be told
> that any order is paid.

## A handler that passes every test below

```javascript
import crypto from "node:crypto";

// The fields, in this order. Empty ones are skipped.
const HASH_FIELDS = [
  "SiteCode",
  "TransactionId",
  "TransactionReference",
  "Amount",
  "Status",
  "Optional1",
  "Optional2",
  "Optional3",
  "Optional4",
  "Optional5",
  "CurrencyCode",
  "IsTest",
  "StatusMessage",
];

function isFromOzow(body) {
  const parts = HASH_FIELDS.map((field) => body[field] ?? "").filter(
    (value) => value !== "",
  );
  // Private key appended, then the whole string lowercased, then SHA512.
  const check = crypto
    .createHash("sha512")
    .update((parts.join("") + process.env.OZOW_PRIVATE_KEY).toLowerCase())
    .digest("hex");
  // Fixed-time compare: a plain === leaks the answer one character at a time.
  const sent = Buffer.from(String(body.Hash ?? "").toLowerCase());
  const ours = Buffer.from(check);
  return sent.length === ours.length && crypto.timingSafeEqual(sent, ours);
}

app.post(
  "/ozow/notify",
  express.urlencoded({ extended: false }),
  (req, res) => {
    // Test 4. Rejected, logged, and never acted on.
    if (!isFromOzow(req.body)) return res.status(400).send("invalid hash");

    res.sendStatus(200);

    const { TransactionId, Status, StatusMessage } = req.body;

    // Test 5. The same notification can arrive more than once.
    if (!claimOnce(TransactionId)) return;

    switch (Status) {
      case "Complete":
        fulfilOrder(TransactionId);
        break;
      case "Pending":
      case "PendingInvestigation":
        // Not an outcome. Leave the order alone.
        break;
      case "Cancelled":
      case "Error":
      case "Abandoned":
        failOrder(TransactionId, StatusMessage);
        break;
      default:
        // A value this code has never seen. Do not guess what it means.
        alertOps(`unknown status ${Status} on ${TransactionId}`);
    }
  },
);
```

`Amount` is hashed with two decimal places, exactly as it was sent. `claimOnce` is whatever makes
the update happen once in your system: a unique constraint on the transaction id, a row lock, or a
conditional write.

> ⚠️ **Important**: Update the order from this handler, never from the browser returning to your
> success URL, and never from an SDK event. A customer who closes the tab still has to end up with
> the right order state.

---

## Core test cases

### Test 1: Successful payment

Verify that your integration handles a completed payment end to end.

**Steps**

1. Create a payment request with
   [`POST /postpaymentrequest`](https://hub.ozow.com/api-reference/payments-api/post-post-payment-request.md), with `NotifyUrl` set
2. Complete the payment with a valid payment method
3. Verify that your endpoint receives the notification
4. Verify that the hash validates
5. Verify that the order is updated
6. Verify that the customer reaches your `SuccessUrl`

**Expected outcomes**

- `Status` is `Complete`
- Order credited and fulfilled once
- Customer redirected to `SuccessUrl`

---

### Test 2: Cancelled payment

Verify that a cancellation does not credit the order.

**Steps**

1. Create a payment request
2. Cancel the payment on the Ozow payment page
3. Verify that your endpoint receives the notification
4. Verify that the order is not credited
5. Verify that the customer reaches your `CancelUrl`

**Expected outcomes**

- `Status` is `Cancelled`
- Order not credited
- Customer redirected to `CancelUrl`

---

### Test 3: Failed payment

Verify that a failure does not credit the order.

**Steps**

1. Create a payment request
2. Attempt a payment that fails
3. Verify that your endpoint receives the notification
4. Verify that the order is not credited
5. Verify that the customer reaches your `ErrorUrl`

**Expected outcomes**

- `Status` is `Error`, with the detail in `StatusMessage`
- Order not credited
- Customer redirected to `ErrorUrl`

---

### Test 4: Hash verification

Verify that verification actually rejects something.

**Steps**

1. Complete a successful test payment and keep the notification body
2. Verify it with your implementation and confirm it passes
3. Change one field, `Amount` or `Status`, leaving `Hash` as it was, and replay it
4. Confirm the tampered notification fails verification and is not processed

**Expected outcomes**

- The genuine notification passes
- The tampered notification is rejected, logged, and updates nothing
- Empty fields are skipped, and the string is lowercased before hashing

---

### Test 5: The same notification twice

Verify that one payment updates one order once.

> ℹ️ **Note**: Ozow may send the same notification more than once. Your handler has to survive it.

**Steps**

1. Complete a successful test payment
2. Deliver the same notification to your endpoint a second time
3. Verify that your handler processes it idempotently

**Expected outcomes**

- The second notification is recognised and changes nothing
- The order is credited once
- Nothing throws

---

### Test 6: Transaction status check

Verify that you can ask, rather than wait.

**Steps**

1. Complete a test payment and note the transaction reference
2. Call
   [`GET /GetTransactionByReference`](https://hub.ozow.com/api-reference/payments-api/get-get-transaction-by-reference.md)
3. Compare the status with the outcome you were sent

**Expected outcomes**

- The call returns the transaction
- Its status matches the notification

---

## Optional test cases

Run these only if you have built the feature.

### Test 7: Standalone button

Verify that a button opens the payment method it names.

**Steps**

1. Build the standalone button with the correct `SelectedBankId`
2. Create a payment request with that field set
3. Verify that the Ozow payment page opens on that payment method

**Expected outcomes**

- The named payment method is shown
- The customer is routed to it without choosing again

---

### Test 8: Customer Identity Verification

Run this only if your business is in a high-risk industry and Customer Identity Verification is
required.

**Steps**

1. Create a payment request with a verified customer identity in `customerIdentifier`
2. Verify that the page offers only payment methods linked to that identity
3. Verify that unlinked payment methods are hidden
4. Create a payment request with no identity and verify which methods are offered

**Expected outcomes**

- Only linked payment methods are offered when an identity is passed
- The behaviour without an identity is what you expect for your account

---

# Background

Context for the above. Nothing here is implemented against.

---

# How Ozow works

> How Ozow connects you to South African banks and payment methods, and the two directions money moves: payins from customers, payouts to recipients.

Source: https://hub.ozow.com/getting-started/

Ozow is a payment infrastructure layer that connects merchants to multiple payment methods and
banking rails. Instead of building separate integrations for each bank or payment method, you
integrate with Ozow to gain access to the full suite of Ozow payment products.

## The two directions of money movement

Every Ozow integration moves money in one of two directions.

**Payin**: a customer pays you. The customer initiates the payment, Ozow processes it, and you
receive the funds. This covers checkout and any other payment collection.

**Payout**: you send funds to a recipient. Your system initiates the transfer, Ozow processes it,
and the recipient receives the funds in their bank account. This covers disbursements, refunds to
bank accounts, and bulk payments.

The distinction runs through everything: different APIs, different credentials, different approval
processes, and a different structure in these docs. Work out which direction you need before you
start. The [Integration methods](https://hub.ozow.com/integration-methods.md) section is
organised around it.

## One integration, every way to pay

Payment methods are enabled on your Ozow account, not in your code.

Pay by Bank is enabled by default. Other methods you opt into (card, PayShap Request, voucher, buy
now pay later, crypto) are enabled by Ozow on your account, and they then appear on the Ozow payment
page automatically. You don't build a new integration or call a different endpoint for each one.

This means you can go live with Pay by Bank and add methods later as a commercial decision rather
than a development project.

## The core payment flows

### Payin

```mermaid
sequenceDiagram
    participant C as Customer
    participant M as Your system
    participant O as Ozow

    C->>M: Reaches checkout
    M->>O: Creates payment request
    O-->>M: Returns payment URL
    M->>C: Sends customer to Ozow
    C->>O: Completes payment
    O-->>M: Notifies your webhook of the outcome
    M->>O: Verifies the status
    M-->>C: Updates the order
```

Two things to notice. The payment request is created by **your server**, never by the customer's
browser. And the outcome arrives on **your webhook**, not in the customer's redirect back to your
site; the customer landing on your success page is not proof of payment. See [Building a secure
integration](https://hub.ozow.com/getting-started/building-a-secure-integration.md).

### Payout

There is no customer-facing step. The whole flow happens between your backend and Ozow.

```mermaid
sequenceDiagram
    participant M as Your system
    participant O as Ozow
    participant R as Recipient bank

    M->>O: Checks payout availability
    M->>O: Sends payout request
    O->>M: Calls your verification webhook
    M-->>O: Confirms the payout
    O->>R: Submits the payout to the bank
    R-->>O: Confirms the outcome
    O-->>M: Notifies your webhook of the final status
```

Before Ozow moves any money, it calls back to your system to confirm the payout is genuine.
If that call fails or can't be reached, the payout does not proceed. That's deliberate, and
it's why payout integrations require testing and sign-off before they go live.

## Getting paid: transactions and settlements

A completed transaction is not money in your bank account. These are two separate stages with two
separate status vocabularies.

```mermaid
flowchart LR
    A["Customer pays"] --> B["Transaction completes"]
    B --> C["Included in a settlement"]
    C --> D["Funds in your bank account"]
```

The transaction status tells you whether the customer's payment succeeded. The settlement status
tells you whether the money has actually reached you. Settlement happens on a delay that depends on
the payment method.

Use transaction status to fulfil orders. Use settlement status to reconcile your bank account. See
[Transaction and settlement statuses](https://hub.ozow.com/integration-methods/statuses.md).

## Paying out: your float

Money leaving Ozow doesn't come out of your incoming payments. It comes from a **float**: a balance
you pre-fund by transferring money to Ozow.

Both payouts and refunds draw on the float. If it's empty, they won't process. Payins don't need a
float at all, so if you're only collecting payments you can ignore this entirely.

See [Float top-up](https://hub.ozow.com/payment-products/settlements-and-float/float-top-up.md).

## Environments

Ozow provides separate staging and production environments. They're completely isolated, and staging
credentials are different from your production credentials.

Testing requirements differ by direction. Payin integrations can go straight to production. We
recommend working through the [payin test
cases](https://hub.ozow.com/integration-methods/testing/payin-test-cases-one-api.md), but you don't need to submit anything.
Payout integrations require mandatory staging testing and formal sign-off from Ozow before they're
enabled in production.

**Getting your credentials:**

- **Production credentials** are available to you directly in the [Ozow
  Dashboard](https://dash.ozow.com). Ozow will never send them to you.
- **Staging credentials** are issued on request. Ask your account manager or contact [support@ozow.com](mailto:support@ozow.com).

> 🚨 Ozow will never share your production credentials with you, and will never ask you for them. If
> anyone contacts you offering to send production credentials, or asking you to share yours, treat
> it as fraudulent and report it to [support@ozow.com](mailto:support@ozow.com).

## Where to go next

How you integrate depends on how much control you want over the payment experience and how much you
want to build, from no-code payment requests through to a full API integration.

Head to [Integration methods: overview](https://hub.ozow.com/integration-methods.md) to choose
the right path.

---

# Prerequisites and onboarding

> What to have in place before you write any code: a merchant account, Dashboard access, your credentials, and payout eligibility if you need it.

Source: https://hub.ozow.com/getting-started/prerequisites-and-onboarding/

Before you start integrating Ozow, make sure you have everything in place. This page covers what you
need before writing a single line of code.

## 1. Register as an Ozow merchant

You need an active Ozow merchant account before you can integrate. If you don't have one yet,
[join our merchant family](https://ozow.com/merchants) or speak to your account manager to get set up.
 If you signed up through a commercial manager or already have an account, you can skip this step and
 log in to the [Ozow Dashboard](https://dash.ozow.com) directly.

If you need assistance with your account, contact [support@ozow.com](mailto:support@ozow.com) or
reach out to your account manager.

## 2. Access the Ozow Dashboard

Once your merchant account is active, you can log in to your [Ozow Dashboard](https://dash.ozow.com).
The Dashboard is where you'll find everything you need to begin your integration.

## 3. Retrieve your credentials

Which credentials you need depends on the API you are integrating against. Collect the row for
yours and ignore the rest.

| | One API | Payments API | Payouts API |
|---|---|---|---|
| Client ID and Client Secret | **Yes** | No | No |
| API key | No | Yes | Yes, a **different** key |
| Private key | No | Yes, to sign the hash | Yes, to sign the hash |
| Where to find them | One API Clients | Merchant Details and Site | Issued once payouts are approved |

**On One API, the Client ID and Client Secret are all you need.** You exchange them for an access
token. There is no API key to send and no hash to compute.

**On the Payments API and the Payouts API you need both keys, and they do different things.** The
API key goes in the `ApiKey` header. The private key is never sent: you use it to compute the
`hashCheck` field on the request, and again to verify the hash on a notification.

**The Payouts API takes its own API key, not the one the Payments API takes.** Sending the Payments
API key to a payout endpoint is rejected.

Your **site code** identifies which of your sites a request belongs to and is in the Site section of
the Dashboard. Every path needs one. It is not a secret.

> ℹ️ **One API clients and payout API keys are scoped per site or per merchant.** A key issued for
> one site does not work for another, so check which you have been given before you assume it covers
> your whole account.

> ⚠️ **Security note**: Keep your credentials secure at all times. Never expose them in client-side
> code, public repositories, or logs. Ozow does not publish credentials publicly and will never ask
> you to share them in an unsecured channel.

## 4. Understand your project setup

When you log in to the Ozow Dashboard, you'll see your merchant account. Within your account, you
can have one or more sites; each representing a separate website, merchant, or integration point.

Each site has its own unique site code, and your site code and API credentials work together to
identify which site a payment belongs to. Payment requests and transactions are always tied to a
specific site, so it's important to use the correct site code for the integration you're building.

```mermaid
graph TD
    A[Ozow Dashboard] --> B[Site 1\nsite code: ABC-001]
    A --> C[Site 2\nsite code: ABC-002]
    A --> D[Site 3\nsite code: ABC-003]
    B --> E[Payments & transactions\ntied to Site 1]
    C --> F[Payments & transactions\ntied to Site 2]
    D --> G[Payments & transactions\ntied to Site 3]
```

## 5. Integrating payouts? Check your eligibility first

If you intend to integrate payouts, you must be approved by Ozow's onboarding team before you can
begin. Payout credentials are not issued until this approval is in place; you will not be able to
start a payout integration without them.

Contact your account manager or [support@ozow.com](mailto:support@ozow.com) to request payout eligibility.

> ℹ️ **Note**: Payin credentials are issued automatically as part of standard merchant onboarding.
> Payout credentials require a separate approval process before they are issued.

## 6. Choose your integration path

Once your credentials are in place, you're ready to choose how you'll integrate. Head to
[choose your integration](https://hub.ozow.com/integration-methods.md) to understand your
options and choose the right path for your use case.

If you're new to Ozow and want to get to your first payment as quickly as possible, go straight to
the [quick start guide](https://hub.ozow.com/getting-started/quick-start.md).

---

# Choosing a checkout experience

> Where your customer pays decides how much you build and whether you take on PCI DSS scope. Compare redirect, embedded and server to server.

Source: https://hub.ozow.com/integration-methods/apis/payin/

If you're building a payin integration with our APIs, your first decision is where the customer
actually pays. That affects how much you build, whether the customer leaves your site, and whether
you take on PCI DSS scope.

> ℹ️ **If you're not sure, use [Redirect to Ozow](https://hub.ozow.com/integration-methods/apis/payin/redirect-to-ozow.md).** It's the fastest to build,
> it supports every payment method we offer, and Ozow carries the PCI DSS scope. Moving to an
> embedded experience later is a real piece of work rather than a switch: the embedded guides are
> Payments API integrations, so the endpoint you post to, the credentials you send and the way you
> verify the notification all change.

## The three approaches

|  | Redirect to Ozow | Embedded | Server-to-server |
|---|---|---|---|
| **Customer leaves your site** | Yes | No | No |
| **Who renders the payment form** | Ozow | Ozow | You |
| **Your PCI DSS scope** | None | None | Full |
| **Payment methods** | Enabled by Ozow on your account | Enabled by Ozow on your account | Enabled by Ozow on your account |
| **Available today** | Yes | Yes | No |
| **Build effort** | Lowest | Moderate | Highest |

All three use the same underlying model: you create a payment request from your server, the customer
authorises it, and Ozow notifies your server of the outcome. What changes is where the
authorisation happens, and, between redirect and embedded, which API you build it against.

## One integration, every payment method

Payment methods are enabled on your Ozow account, not in your code.

**Pay by Bank is enabled by default.** Any other methods you opt into, card, PayShap request,
voucher, buy now pay later, crypto; are enabled by Ozow on your account. Once enabled, they appear
on the Ozow payment page automatically. You don't build a new integration, call a different
endpoint, or ship code for each one.

This means you can start with Pay by Bank, go live, and add methods later as a commercial decision
rather than a development project.

Two things to know:

- **[Standalone buttons](#standalone-payment-buttons) are optional extra work.** They're recommended
  for conversion, but each one is a per-method build rather than something enabled on your account.
- **Apple Pay and Google Pay ride along with card**: they don't have their own `institutionId`. Once
  card and wallet payments are enabled on your account, they appear on the Ozow payment page
  automatically; on the default selection screen and on the card payment screen, for customers whose
  device and browser support them. To render them directly on your own checkout page instead, use
  the [embedded wallet](https://hub.ozow.com/integration-methods/apis/payin/embedded-wallet.md).

To opt into a payment method, speak to your Ozow account manager. See [Prerequisites and onboarding](https://hub.ozow.com/getting-started/prerequisites-and-onboarding.md).

## Redirect to Ozow

You create a payment request server-side and send the customer to the URL Ozow returns. They
complete the payment on an Ozow-hosted page, then return to your site.

**Choose redirect if:**

- You want the shortest path to a working integration
- You want every enabled payment method available without extra work
- You don't want payment details touching your infrastructure
- You're integrating a backend system, an invoicing flow, or anything without a browser checkout of
  its own

**Look elsewhere if:** keeping the customer on your own domain throughout is a hard requirement.

→ [Redirect to Ozow](https://hub.ozow.com/integration-methods/apis/payin/redirect-to-ozow.md)

### Standalone payment buttons

By default, the Ozow payment page asks the customer to choose how they want to pay. If you'd rather
show your own buttons; "Pay with Capitec Pay", "Pay with card"; and send the customer straight to that
method, pass the relevant `institutionId` when you create the payment request. The customer skips
the selection screen.

This works with both redirect and embedded checkouts.

The identifier for each one is in
[Payment method identifiers](https://hub.ozow.com/integration-methods/apis/payin/payment-method-ids.md), where the **Standalone button**
column marks the methods you can put your own button behind.

> ℹ️ There's no `institutionId` for Apple Pay or Google Pay, and you don't need one. Once card and
> wallet payments are enabled on your account, the wallet buttons appear alongside card entry; both
> on the default selection screen and on the card payment screen. If you want a standalone card
> button, use the **Card** `institutionId` and the wallet buttons come with it. To render them on
> your own checkout page instead, use the [embedded wallet](https://hub.ozow.com/integration-methods/apis/payin/embedded-wallet.md).

> ⚠️ If you replace the Ozow selection screen entirely with your own buttons, newly enabled payment
> methods won't appear until you add a button for them. Worth keeping in mind if you plan to add
> methods over time.

> ℹ️ Several bank methods require **Customer Identity Verification** if you operate in a high-risk
> industry. Check [Customer Identity Verification](https://hub.ozow.com/integration-methods/apis/payin/identity-verification.md) before
> building standalone buttons for them.

## Embedded

The customer never leaves your page. Ozow still renders the payment form, so you take on no PCI DSS
scope; you're hosting it inside your own layout.

**All three are Payments API integrations**, unlike the redirect above, which is One API. The
payment request, the credentials and the notification check are the Payments API's throughout.

There are three variants.

|  | iframe | Modal | Embedded wallet |
|---|---|---|---|
| **What the customer sees** | Ozow checkout inside a container on your page | Ozow checkout in an overlay above your page | Apple Pay, Google Pay and card, in an iframe on your page |
| **Container element needed** | Yes | No | No |
| **Payment methods** | All enabled on your account | All enabled on your account | Apple Pay, Google Pay, card |
| **Requires** | Ozow SDK and jQuery | Ozow SDK and jQuery | Ozow Wallet SDK |
| **Additional setup** | None | None | Card and wallet payments enabled on your account, Apple Pay domain verification, CSP allowances |

**Choose iframe** if you want the payment form to sit inline in your checkout page as part of the layout.
→ [Embedded iframe](https://hub.ozow.com/integration-methods/apis/payin/embedded-iframe.md)

**Choose modal** if you'd rather trigger checkout from a button and have it appear over your page.
Same SDK, no container markup to manage. → [Embedded Modal](https://hub.ozow.com/integration-methods/apis/payin/embedded-modal.md)

**Choose the embedded wallet** if you want Apple Pay, Google Pay and card payments rendered directly
on your own checkout page rather than on an Ozow screen. Card and wallet payments must be enabled on
your account by Ozow first. It covers those three methods only; if you also need Pay by Bank or
anything else, combine it with redirect or one of the other embedded options. → [Embedded
Wallet](https://hub.ozow.com/integration-methods/apis/payin/embedded-wallet.md)

> ⚠️ The iframe and modal checkouts require jQuery on the page and must be loaded through the Ozow
> SDK. Don't build your own iframe around a payment URL, it won't behave correctly and it isn't
> supported.

## Server-to-server

You collect the customer's payment details on your own page and post them to Ozow from your server.
Ozow renders nothing.

> ⚠️ **Not available yet.**

Because you'd be handling raw payment details, you'll need to be PCI DSS compliant to use this. If
you're evaluating it, speak to your Ozow account manager early; compliance is usually the longest
part of the project.

## Native mobile apps

Ozow doesn't provide a native iOS or Android SDK. For payments inside a mobile app, use [Redirect to
Ozow](https://hub.ozow.com/integration-methods/apis/payin/redirect-to-ozow.md) and open the payment URL in a system browser or web view.

> ⚠️ The embedded wallet SDK is a web SDK. It is not supported inside native mobile applications.

## What every approach still needs

Whichever you pick, these don't change:

- **Create the payment request from your server**, never from the browser. Your API credentials must
  never reach client-side code.
- **Treat the webhook as the source of truth**, not the customer's browser returning to your success
  page. See [Building a secure
  integration](https://hub.ozow.com/getting-started/building-a-secure-integration.md).
- **Handle every status**, not just success and failure. See [Transaction and settlement statuses](https://hub.ozow.com/integration-methods/statuses.md).

## Still deciding?

- Building a normal online checkout and want it working quickly → **Redirect**
- Customers must not leave your domain → **Embedded iframe** or **Modal**
- You want Apple Pay and Google Pay on your own page → **Embedded wallet**
- You want your own branded button per bank or method → **Redirect or Embedded, with standalone buttons**
- You're PCI DSS compliant and want full control of the form → **Server-to-server**, when it's released

---

# Hash calculator

> Work out the hashCheck a request carries, and see the exact string it is computed from.

Source: https://hub.ozow.com/integration-methods/apis/deprecated-integrations/hash-calculator/

Every request that moves money carries a `hashCheck`, and a request whose hash does not match is
rejected. The rejection does not say which field was wrong, so this shows you the string the hash is
computed from, one field at a time.

It works in both directions. **Build it** fills in the fields you send and shows the string they
concatenate to. **Check mine** goes the other way: give it what your own code produced, and it says
where that leaves the documented string and names the mistake that would explain it.

> ⚠️ **Important**: Neither of those needs your private key, and the page asks for one only if you
> choose the last of three options. It builds the string with a placeholder in place of the key,
> which is all you need: a hash is determined entirely by the string it is computed from, so if your
> string matches this one, your hash matches too. Comparing strings finds every mistake except a
> wrong key, because every other mistake changes the string before the key is reached.

> ⚠️ **Before you paste a key anywhere**: check the address bar. This page is the only one on this
> site that will ever ask for one, it holds it in the tab and nowhere else, and it still asks you to
> try the string comparison first. A page imitating this one would ask sooner and explain less.

[Hash calculator](https://hub.ozow.com/integration-methods/apis/deprecated-integrations/hash-calculator/), a tool on this page.

## What usually goes wrong

The key is rarely the problem. In order of how often they occur:

| Cause | What it looks like |
|---|---|
| **Field order** | The fields are concatenated in a fixed order, not the order your object happens to serialise in. Reordering them changes the hash. |
| **Amount format** | A payin writes the amount with two decimals, `100.00`. A payout writes it in cents, `10000`. |
| **A blank optional field** | An empty field contributes nothing at all. It does not contribute a placeholder, a space or the word `null`. |
| **Lowercasing** | Payin, payout and verification hashes lowercase the whole concatenated string, including the key. Refunds do not: see below. |
| **The key itself** | Test and production keys differ. A hash built with the wrong one fails in exactly the same way as a hash built in the wrong order. |

## Three things the notification hashes do differently

- **The payout notification takes `customerMerchantReference`**, where the payout request takes
  `customerBankReference`. Check which one you are reading before you hash it.
- **The payout notification's two statuses go in as integers**, not as their names.
- **A voucher payout appends the voucher pin after the key**, so the key is not last for that one hash.

The refund notification also arrives with the account number already masked, so verify it with the
masked value you received rather than the number you sent.

## Refunds do not lowercase

Payin, payout and payout verification hashes lowercase the entire concatenated string before
hashing. **A refund hash does not.**

Apply the payin rule to a refund and your hash is rejected the moment your refund reason or notify
URL contains a capital letter. Build the refund string exactly as your values are, with no
lowercasing.

## Which direction the hash goes

A **request** hash is one you build and send. Ozow rejects the request if it does not match.

A **notification** hash is one you check on something Ozow sent you, and **its field order is not
the same as the request's**. Reusing a request's order to verify an incoming notification rejects
every notification you receive, which is the single most expensive way to get this wrong: your
integration looks fine until money starts moving.

## One API does not use a hash

One API authenticates every call with an OAuth 2.0 bearer token, and its requests carry no
`hashCheck` field.

Its webhooks are not verified with a hash either. One API delivers them through
[Svix](https://www.svix.com/), which signs each one with an HMAC over the `svix-id`,
`svix-timestamp` and body. Verify it with the secret from the Get Webhook Secret endpoint, using the
Svix libraries. [Redirect to Ozow](https://hub.ozow.com/integration-methods/apis/payin/redirect-to-ozow.md) covers the
headers and how to check them.

Use this page for the Payments API and the Payouts API.

## Where each hash is used

| Request | Concatenates | Key |
|---|---|---|
| Payments API payin request | 32 fields, from `siteCode` to `tokenProfileId` | Your private key |
| Payments API payin notification | 13 fields, from `siteCode` to `statusMessage` | Your private key |
| Payments API refund request | `transactionId`, `amount`, `refundReason`, `notifyUrl` | Your private key |
| Payments API refund notification | 8 fields, from `refundId` to `statusMessage` | Your private key |
| Payouts API payout request | 11 fields, from `siteCode` to `identityType` | Your API key |
| Payouts API payout verification | The payout fields, with `payoutId` in front | Your API key |
| Payouts API payout notification | 6 fields, from `payoutId` to `payoutStatus.subStatus` | Your API key |

The payin hash is described in full in [Redirect to
Ozow](https://hub.ozow.com/integration-methods/apis/payin/redirect-to-ozow.md), and the payout hash in [Send a
payout](https://hub.ozow.com/integration-methods/apis/payout/send-a-payout.md).

> ℹ️ **Note**: Compute the hash on your server, never in browser JavaScript. A hash built in the
> browser needs the private key in the browser, and anything that reaches the client can be read by
> anyone holding the client.

---

# Building a secure integration

> Where Ozow's security responsibility ends and yours begins: credentials, webhook endpoints, verifying notifications, and validating amounts.

Source: https://hub.ozow.com/getting-started/building-a-secure-integration/

Security is a shared responsibility between Ozow and you as the merchant. Understanding where Ozow's
responsibility ends and yours begins is essential to building an integration that is safe for your
customers and your business.

## The shared security model

Ozow secures the payment infrastructure. You secure how you integrate with it.

| Ozow is responsible for | You are responsible for |
|---|---|
| The security of the payment processing infrastructure | How you store and handle your API credentials |
| Encryption of payment data in transit and at rest within Ozow systems | The security of your callback and webhook endpoints |
| The integrity and availability of Ozow APIs | Verifying that notifications genuinely came from Ozow |
| Fraud monitoring within the Ozow platform | Validating transaction details before crediting orders |
| Physical and network security of Ozow's environments | Access controls on your systems and Ozow Dashboard |
| Compliance with applicable payment regulations on Ozow's side | Monitoring your own integration for anomalous activity |

A secure Ozow integration is not only about calling the right endpoints, it is about what happens on
your side of the connection too.

## Your security responsibilities

### Credentials and secrets

Your API credentials are the keys to your Ozow integration. If they are compromised, an attacker
could initiate payments or payouts on your behalf.

- Store all Ozow credentials, API keys, private keys, Client IDs, Client Secrets, and Payout API
  keys; in a secrets manager or environment variables. Never hardcode them or commit them to source
  control.
- Keep test and production credentials in strictly separate environments. Never use production
  credentials in a development or staging environment.
- Restrict access to production credentials to a named list of people and services. Access must be least-privilege.
- Have a documented process and a named owner for rotating credentials. Know what you would do if a
  key were compromised.

### Callback and webhook endpoint security

Ozow communicates payment outcomes by sending notifications to a URL you specify. This endpoint is a
critical part of your integration.

- Your callback and webhook URLs must be HTTPS only, using TLS 1.2 or later.
- Your endpoint must not expose stack traces, internal errors, or verbose logs in its response to callers.

### Verifying notifications

Receiving a notification is not the same as trusting it. You must verify that every notification
genuinely came from Ozow before acting on it.

- For Payments API integrations: verify every incoming notification using the hash check before
  updating any order status.
- For One API integrations: validate the message signature on every incoming webhook before acting
  on it.
- Log and alert on verification failures rather than silently discarding them. A pattern of
  verification failures is a signal worth investigating.
- Never mark a payment as complete based on the browser redirect alone. Always confirm status via
  the API or a verified webhook notification.
- Implement replay protection so that a previously processed transaction reference cannot be
  reprocessed to double-credit an order.

> ⚠️ **Important**: Ozow may occasionally send duplicate notifications for the same transaction.
> Your system must handle this gracefully, processing the same transaction twice must not result in
> double-crediting an order.

### Transaction integrity

Before crediting an order, validate that the payment details match what you originally requested.

- Verify that the amount, currency, and merchant reference in the notification match your original
  payment request.
- Handle duplicate notifications idempotently, receiving the same notification twice must have no
  additional effect.
- Periodically reconcile your order records against Ozow's transaction records rather than relying
  solely on webhook delivery.

### Payout-specific responsibilities

Payouts carry additional security requirements because they involve outgoing funds.

**Authorisation**

- For bulk payouts: Ozow recommends that the person who requests a bulk payout is different from the
  person who approves it. Ozow does not enforce this.
- For API payouts: access to the systems, credentials, and code that can trigger a payout must be
  restricted to a named list of people, with any changes requiring review.
- Your system must enforce a business-level authorisation step before calling Ozow's payout API.
  Being authenticated is not sufficient, there must be a deliberate approval within your own system
  before a payout is initiated.

**Beneficiary handling**

- Verify beneficiary bank details before the first payout to any new beneficiary.
- If you are not using stored beneficiary profiles, validate destination bank details on every
  payout request.
- Any change to stored beneficiary details must trigger a mandatory review or re-verification step
  before the next payout.
- Generate and persist a unique encryption key per payout request. Never reuse an encryption key
  across multiple payout requests.
- Enforce velocity and amount limits on payouts.
- Implement real-time alerting for anomalous payout activity, unusual amounts, unfamiliar
  beneficiaries, or off-hours activity are all signals worth acting on immediately.

**Verification request handling**

- Validate the access token on all incoming payout verification requests.
- Verify the hash on every verification request to confirm it genuinely originated from Ozow.
- Validate that the payout details in the verification request match a payout your system actually
  initiated: do not return a decryption key based on token and hash checks alone without confirming
  the payout is expected.

**Payout status verification**

- Confirm payout completion via the API, rather than assuming completion from the initial payout response.
- Verify the hash on every incoming payout status notification before trusting it.

### Access and monitoring

- Apply least-privilege access for all roles with access to your Ozow merchant Dashboard.
- Monitor for abnormal patterns in your payin traffic, spikes in failed verifications or unusual
  volumes are worth investigating.
- Maintain an immutable audit trail of who requested and who approved every payout, and when.
- Reconcile your internal ledger against Ozow's payout records on a regular cadence.

## Quick reference checklist

Use this checklist before going live with any Ozow integration.

### Payin integrations

- [ ] API credentials are stored securely and never hardcoded or committed to source control
- [ ] Test and production credentials are in strictly separate environments
- [ ] Production credentials are restricted to a named list of people and services
- [ ] Credential rotation process is documented with a named owner
- [ ] Callback URL is HTTPS only with TLS 1.2 or later
- [ ] Callback endpoint does not expose internal errors or stack traces
- [ ] Every notification is verified using hash check (Payments API) or message signature (One API)
  before being trusted
- [ ] Verification failures are logged and alerted on
- [ ] Payment status is confirmed via API, not the browser redirect alone
- [ ] Replay protection is in place for transaction references
- [ ] Amount, currency, and merchant reference are validated against the original request before crediting
- [ ] Duplicate notifications are handled idempotently
- [ ] Order records are periodically reconciled against Ozow transaction records
- [ ] Dashboard access follows least-privilege
- [ ] Monitoring is in place for anomalous payin traffic

### Payout integrations

- [ ] Payout API key is stored securely and never hardcoded or committed to source control
- [ ] Test and production payout credentials are in strictly separate environments
- [ ] Access to payout-triggering systems and code is restricted to a named list
- [ ] Bulk payout requestor and approver are different people (recommended, not enforced by Ozow)
- [ ] Business-level authorisation step is enforced before calling the payout API
- [ ] Beneficiary bank details are verified before the first payout to any new beneficiary
- [ ] A unique encryption key is generated and persisted per payout request
- [ ] Velocity and amount limits are enforced on payouts
- [ ] Real-time alerting is in place for anomalous payout activity
- [ ] Incoming verification requests are validated on token, hash, and expected payout details
- [ ] Payout completion is confirmed via API, not assumed from the initial response
- [ ] Payout status notifications are verified by hash before being trusted
- [ ] An immutable audit trail exists for every payout
- [ ] Internal ledger is reconciled against Ozow payout records regularly

---

# The contract

The operations those pages declare, as the specification defines them.

---

# Get Transaction By Reference

> GET `/GetTransactionByReference`
> Part of the Payments API reference. Source: https://hub.ozow.com/api-reference/payments-api/get-get-transaction-by-reference/

Server: `https://api.ozow.com` (Production)

Other environments: `https://stagingapi.ozow.com` (Staging)

This operation is used  when you want to query transactions using the merchant's transaction reference, specified when creating the payment request. 
This method is able to return multiple results. Ozow does not restrict the merchant from sending duplicate merchant references, though it is advised that a unique reference is sent per transaction. The number of results returned are limited to 10.
Note that the site code must be the same as the one used when the associated payment request was created.

## Authentication

- `ApiKey` (API key in the ApiKey header)
  - The unique API key for the merchant. See [Prerequisites and onboarding](../../getting-started/prerequisites-and-onboarding.md) for where to find it.

## Query parameters

- `siteCode` (string, required) - A unique code for the each of the merchant's sites. A site code is generated when adding a site in the Ozow merchant admin section.
- `transactionReference` (string, required) - The merchant's reference for the transaction.
- `isTest` (boolean) - Defaults to false. Use true only to get results for test requests.

## Request body

This method is called when you want to query transactions using the merchant's reference. This method is able to return multiple results. Ozow does not restrict the merchant from sending duplicate merchant references, though it is advised that a unique reference is sent per transaction. The number of results returned are limited to 10.

## Responses

### 200 Array of TransactionModel

**application/json**

array of TransactionModel

**application/xml**

array of TransactionModel

### 401 Unauthorized. The `ApiKey` header is missing or does not match the site, or for a `Secure` operation the bearer token is missing, expired or invalid.

string

Example (example 1):

```json
API key is missing or invalid.
```

### 403 Forbidden. The credentials were readable but the site cannot be authorised, because no merchant matches the site code or the merchant is deactivated.

string

Example (example 1):

```json
Merchant for site code TSTSTE0001 is deactivated
```

### 500 Internal Server Error. Something failed on the Ozow side.

string


---

# Create Payment Request

> POST `/postpaymentrequest`
> Part of the Payments API reference. Source: https://hub.ozow.com/api-reference/payments-api/post-post-payment-request/

Server: `https://api.ozow.com` (Production)

Other environments: `https://stagingapi.ozow.com` (Staging)

Creates a payment request with the requested parameter set.

## Authentication

- `ApiKey` (API key in the ApiKey header)
  - The unique API key for the merchant. See [Prerequisites and onboarding](../../getting-started/prerequisites-and-onboarding.md) for where to find it.

## Request body

**application/json**

- `siteCode` (string, required, max length 50) - A unique code for the site currently in use. A site code is generated when adding a site in the Ozow merchant admin section.
- `countryCode` (string, required, max length 2, pattern ^[A-Z]+) - The ISO 3166-1 alpha-2 code for the user's country. The country code will determine which banks will be displayed to the customer. Please note only South African (ZA) banks are currently supported by Ozow.
- `currencyCode` (string, required, max length 3, pattern ^[A-Z]+) - The ISO 4217 three-letter code for the transaction currency. Please note only the South African Rand (ZAR) is currently supported by Ozow, so any currency conversion must take place before posting to the Ozow site.
- `amount` (number, double, required) - The transaction amount. The amount is in the currency specified by the currency code posted.
- `transactionReference` (string, required, max length 50) - The merchant's reference for the transaction. This reference can be used to look up the transaction with the `GetTransactionByReference` operation.
- `bankReference` (string, required, max length 20) - The reference that will be pre-populated in the "their reference" field in the customers online banking site. This is the payment reference that appears on the merchant’s bank statement and can be used for recon purposes. Only alphanumeric characters, spaces, and dashes are allowed.
- `optional1` (string, max length 50) - Optional field the merchant can post for additional information they would need passed back in the response. These are also stored with the transaction details by Ozow, and can be useful for filtering transactions in the merchant admin section.
- `optional2` (string, max length 50) - Optional field the merchant can post for additional information they would need passed back in the response. These are also stored with the transaction details by Ozow, and can be useful for filtering transactions in the merchant admin section.
- `optional3` (string, max length 50) - Optional field the merchant can post for additional information they would need passed back in the response. These are also stored with the transaction details by Ozow, and can be useful for filtering transactions in the merchant admin section.
- `optional4` (string, max length 50) - Optional field the merchant can post for additional information they would need passed back in the response. These are also stored with the transaction details by Ozow, and can be useful for filtering transactions in the merchant admin section.
- `optional5` (string, max length 50) - Optional field the merchant can post for additional information they would need passed back in the response. These are also stored with the transaction details by Ozow, and can be useful for filtering transactions in the merchant admin section.
- `customer` (string, max length 100) - The customer’s name or identifier.
- `cancelUrl` (string, uri, max length 150) - The URL to which the redirect result should be posted to if the customer cancels the payment. This is also the page the customer will be redirected to. This URL can also be set for the applicable merchant site in the merchant admin section. If a value is set in the merchant admin and sent in the post, the posted value will be redirected to if the payment is cancelled.
- `errorUrl` (string, uri, max length 150) - The URL to which the redirect result should be posted if an error occurs while trying to process the payment. This is also the page the customer will be redirected to. This URL can also be set for the applicable merchant site in the merchant admin section. If a value is set in the merchant admin and sent in the post, the posted value will be redirected to if an error occurred while processing the payment.
- `successUrl` (string, uri, max length 150) - The URL to which the redirect result should be posted to if the payment is successful. This is also be the page the customer gets redirected to. This URL can also be set for the applicable merchant site in the merchant admin section. If a value is set in the merchant admin and sent in the post, the posted value will be redirected to if the payment was successful. Please note that it is not sufficient to assume that the payment was successful simply because the customer has been redirected back to this page. It is highly recommended that you check the response fields as well as the transaction status using our check transaction status API call.
- `notifyUrl` (string, uri, max length 150) - The URL that the notification result should be posted to. The result will post regardless of the outcome of the transaction. This URL can also be set for the applicable merchant site in the merchant admin section. If a value is set in the merchant admin and sent in the post, the notification result will be sent to the posted value. Find out more in the notification response section in step 2.
- `isTest` (boolean, required) - Accepted values are true or false. Send true to test your request posting and response handling. If set to true you will be redirected to select whether you would like a successful or unsuccessful redirect response sent back. Please note that notification responses are sent for test transactions and the online banking payment is skipped.
- `selectedBankId` (string, uuid) - If the 'SelectedBankId' field is populated by the Merchant, the Customer will be redirected to the Ozow login page of the selected bank. However, if the field is left empty, the Customer will be presented with Ozow bank selection screen. See [Payment method identifiers](../../integration-methods/apis/payin/payment-method-ids.md) for the value to send.
- `bankAccountNumber` (string, max length 20) - The bank account number the payment should be made to.
- `branchCode` (string, max length 10) - The branch code for the bank account.
- `bankAccountName` (string, max length 50, pattern ^[a-zA-Z0-9\s]+$) - The name of the beneficiary account the payment is made into. Letters, digits and spaces only. Required, along with `bankAccountNumber`, `branchCode` and `bankId`, whenever any one of them is sent.
- `payeeDisplayName` (string, max length 50) - The name shown on the site as the entity being paid (not in banking screens).
- `expiryDateUtc` (string, max length 19) - Payment will not be allowed to be made after this date. Date should be UTC and value should be formatted as yyyy-MM-dd HH:mm
- `allowVariableAmount` (boolean) - Allows the user to change the amount passed through before paying. This option must also be enabled for the site in the merchant admin portal to be used. Accepted values are true or false. DO NOT include false in the hash check string, just ignore instead.
- `variableAmountMin` (number, double) - If AllowVariableAmount is passed through as true, this will dictate the lowest acceptable amount the user can enter.
- `variableAmountMax` (number, double) - If AllowVariableAmount is passed through as true, this will dictate the highest acceptable amount the user can enter.
- `customerIdentifier` (string, max length 13) - Merchants classified as high-risk must provide a valid South African identity number. It's important to note that this is an optional field for all other merchants. Capitec Pay is the bank this most often applies to; see [Payment method identifiers](../../integration-methods/apis/payin/payment-method-ids.md) for what needs approval before you build against it, and reach out to [support@ozow.com](mailto:support@ozow.com) for whether your account is classified this way.
- `customerCellphoneNumber` (string, max length 10, pattern ^[0-9]+) - Merchant can provide customer cellphone number for faster login on certain banks. DO NOT include in the hash check string, just ignore instead.
- `hashCheck` (string, required, max length 250) - SHA512 hash used to ensure that certain fields in the message have not been altered after the hash was generated. See [Generate the hash check](../../integration-methods/apis/deprecated-integrations/redirect-to-ozow.md#step-1-generate-the-hash-check) for the field order and a worked example.

**application/xml**

`PaymentRequest`, the same schema listed in full earlier in this document. Its own page: https://hub.ozow.com/api-reference/payments-api/schemas/payment-request.md

## Responses

### 200 OK. The request reached the API, which is not the same as it being accepted. A rejected request is also a 200, with the reason in `errorMessage` and no `url`. Check that field, not the status code.

**application/json**

- `paymentRequestId` (string, uuid, required, max length 50) - Ozow's unique identifier for the payment request.
- `url` (string, uri, required, max length 100) - Generated URL that allows payment for the request used to create the payment. You will need to redirect the payer to this URL, who upon completion of the payment will be redirected back to your site. **The payment Url you'll receive from the API is dynamic. Please do not hard code it into your integrations as it might change.**
- `errorMessage` (string, max length 50) - Error message generated when validating the request.

**application/xml**

`PaymentRequestResult`, the same schema listed in full earlier in this document. Its own page: https://hub.ozow.com/api-reference/payments-api/schemas/payment-request-result.md

### 401 Unauthorized. The `ApiKey` header is missing or does not match the site, or for a `Secure` operation the bearer token is missing, expired or invalid.

string

Example (example 1):

```json
API key is missing or invalid.
```

### 403 Forbidden. The credentials were readable but the site cannot be authorised, because no merchant matches the site code or the merchant is deactivated.

string

Example (example 1):

```json
Merchant for site code TSTSTE0001 is deactivated
```

### 500 Internal Server Error. Something failed on the Ozow side.

string


---

# Transaction notification

> POST to your notification URL
> Sent by Ozow. Part of the Payments API reference. Source: https://hub.ozow.com/api-reference/payments-api/webhooks/transaction-notification/

Sent to the notification URL once a transaction reaches a final status.

The URL comes from the `NotifyUrl` field on the payment request, or from the site configuration in the merchant admin site. Without one, no notification is sent.

Verify the `Hash` field before acting on the contents. A notification is an unauthenticated POST to a URL that anyone can call.

## Authentication

Ozow sends no credential with this call, so this check is the only thing standing between a real delivery and a stranger’s. Verify the Hash field before acting on the contents: your notification URL is public, and anyone can post to it.

## Payload

**application/x-www-form-urlencoded**

- `SiteCode` (string, required, max length 50) - The site code sent to Ozow in the request post.
- `TransactionId` (string, uuid, required, max length 50) - The transaction identifier generated by Ozow.
- `TransactionReference` (string, required, max length 50) - The merchant's transaction reference sent in the request post's TransactionReference variable.
- `Amount` (number, double, required) - The transaction amount, always written with two decimal places. That is the form the hash is built from, so use the value exactly as it was posted.
- `Status` (string, required, max length 50) - The transaction status. Possible values are: 1. Complete - The payment was successful. 2. Cancelled - The payment was cancelled. 3. Error - An error occurred while processing the payment. 4. Abandoned – The payment was abandoned. 5. PendingInvestigation – An inconclusive result was received by the bank and the payment needs to be verified manually. 6. Pending – The status cannot be determined as yet but will be reposted to the notification URL as soon as it has been determined. Merchants not using the notification URL will receive a PendingInvestigation status.
- `Optional1` (string, max length 50) - Optional fields sent in the request post.
- `Optional2` (string, max length 50) - Optional fields sent in the request post.
- `Optional3` (string, max length 50) - Optional fields sent in the request post.
- `Optional4` (string, max length 50) - Optional fields sent in the request post.
- `Optional5` (string, max length 50) - Optional fields sent in the request post.
- `CurrencyCode` (string, required, max length 3, pattern ^[A-Z]+) - The transaction currency code sent in the request post.
- `IsTest` (string, max length 5) - Whether the transaction was a test transaction, sent as `True` or `False`. Part of the hash, so use the value exactly as it was posted.
- `StatusMessage` (string, max length 500) - A message about the status, empty for most transactions. Part of the hash, so an empty value still counts as a field and contributes an empty string.
- `Hash` (string, required, max length 128) - SHA512 hash used to ensure that certain fields in the message have not been altered after the hash was generated. See the generate hash section for more details on how to validate the response variables using the hash.
- `SubStatus` (string, max length 50) - The transaction sub status for failed transactions. The value provides an indication as to why the payment failed. Some examples: • Unclassified – Failure scenario has not been mapped • InsufficientFunds - User did not have sufficient funds available to complete the payment While there are several sub-statuses, they have not been included here as they are strictly for reporting.
- `MaskedAccountNumber` (string, max length 50) - The masked account number the payment was made from. If account number is 12 or more digits then the first and last four digits are unmasked e.g. 1234567898765 will be masked as 1234*****8765 If the account number is less than12 digits then the first and last 3 digits are left unmasked e.g. 123456789 will be masked as 123***789 **This is not available by default and a request by the merchant must be submitted along with a justification for requiring this information.**
- `BankName` (string, max length 50) - The name of the bank the payment was made from.
- `SmartIndicators` (string, max length 500) - Some Ozow merchants have requested this information as they use this in their own processes. The can be ignored unless you have a purpose and application for this information. The application of these indicators are for the merchant’s discretion and in isolation do not constitute any action to be taken by the merchant. The field will contain a pipe delimited list of the following values if they are applicable e.g. HIGH_VALUE | FIRST_OZOW : * HIGH_VALUE – If a soft limit is configured on the site and the amount paid is higher or equal to the configured limit * FIRST_OZOW – First time a user has paid using Ozow * FIRST_MERCHANT – First time a user has paid the merchant using Ozow * NEW_OZOW – User has paid using Ozow for the first time in the past seven days * NEW_MERCHANT - User has paid the merchant using Ozow for the first time in the past seven days

## Your response

### 200 Acknowledged. Return this once you have stored the notification.

No body.
