# Server to server payments

> Capture payment details on your own checkout, submit them to Ozow from your backend, and drive the rest of the transaction over a WebSocket. Not yet available for merchant integrations.

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

> 🚧 **Not yet available**: Server to server payments is not yet available for merchant integrations.
> This page explains what server to server payments is and what you will need to prepare, including
> the WebSocket exchange that drives a transaction to completion. Contact
> [support@ozow.com](mailto:support@ozow.com) to register your interest.

## What is server to server payments?

Server to server payments is an advanced integration path where your platform captures payment details
directly and submits them to Ozow via API in the background. Your customer never leaves your site or
sees an Ozow-hosted interface, the entire payment experience is built and controlled by you.

This is the highest level of control available in an Ozow integration and is suited to merchants who
need the payment to happen entirely within their own interface with no Ozow UI involved.

## Requirements

### PCI DSS certification: mandatory

> 🚨 **PCI DSS certification is mandatory for server to server payments. There are no exceptions.**

Because your platform captures and transmits sensitive payment details, you are responsible for
ensuring that data is handled securely. That covers both halves of this integration:

- **Card details**, including card numbers and CVV codes, where you submit them in `paymentDetail`
- **The payer's internet banking credentials**, which the screen-scraping flow collects through the
  WebSocket exchange below

Ozow will require proof of PCI DSS certification before enabling server to server payments on your
merchant profile.

Merchants who are not PCI DSS certified must use one of the following integration paths instead:

- [Redirect to Ozow](https://hub.ozow.com/integration-methods/apis/payin/redirect-to-ozow.md), Ozow handles all sensitive payment data on its own
  hosted page
- [Embedded iframe](https://hub.ozow.com/integration-methods/apis/payin/embedded-iframe.md), full payment page embedded inside your site via the Ozow
  SDK
- [Embedded wallet](https://hub.ozow.com/integration-methods/apis/payin/embedded-wallet.md), card data is captured inside the Ozow iframe, keeping your
  checkout page out of PCI scope

### Additional requirements

- An active Ozow merchant account with Direct integration enabled
- PCI DSS certification, proof required before enablement
- A server-side integration, server to server payments calls must be made server-side only. Never submit
  payment details from browser code.
- A client that can send and receive text frames over a WebSocket and can set a request header on
  the opening handshake, for the exchange described below

## How server to server payments will work

When available, server to server payments will use One API. Instead of calling `/payments` to
generate a redirect URL, you will call `/transactions` directly and supply the payment details in a
`paymentDetail` object:

```json
{
  "paymentRequest": {
    "siteCode": "YOUR_SITE_CODE",
    "amount": {
      "currency": "ZAR",
      "value": 100.00
    },
    "merchantReference": "ORDER-001",
    "expireAt": "2026-12-31T23:59:59Z"
  },
  "paymentDetail": {
    "paymentType": "card",
    "details": {
      "cardNumber": "XXXX XXXX XXXX XXXX",
      "fullName": "CUSTOMER NAME",
      "expiryMonth": "12",
      "expiryYear": "30",
      "cvv": "XXX",
      "userAgent": "CUSTOMER_BROWSER_USER_AGENT",
      "callbackUrl": "https://yourstore.com/card-callback"
    }
  }
}
```

The response carries a `requiredActionOptions` array saying what has to happen next. Your
integration must handle these programmatically, and the entry whose `action` is `websocket` is the
one described below.

## Driving the transaction over a WebSocket

Ozow tells you what the payer must supply next, you render it in your own interface and send the
answer back, and that repeats until the transaction completes.

This is a conversation Ozow drives. You do not decide the steps: the bank does, and they differ
between banks and between payers. Your job is to render whatever arrives and return what the payer
entered.

> ℹ️ **There is no fixed URL to connect to.** The address is issued per transaction and comes back in
> the response that created it, so you cannot configure it ahead of time.

### How the exchange works

```mermaid
sequenceDiagram
    participant P as Payer
    participant Y as Your application
    participant O as Ozow

    Y->>O: POST /transactions
    O-->>Y: requiredActionOptions, action websocket, uri
    Y->>O: Connect to uri with a bearer token
    O-->>Y: InputResponse, what the payer must supply
    Y->>P: Render the step
    P->>Y: Completes it
    Y->>O: InputRequest, the values entered
    Note over Y,O: Repeats for as many steps as the bank asks for
    O-->>Y: CompletionResponse, the final status
```

Four messages travel on the channel. Ozow sends three of them and you send one.

| Message | Direction | What it means |
|---|---|---|
| `InputResponse` | Ozow sends | The payer must supply something. This arrives first and again after each step |
| `InputRequest` | You send | The values the payer entered |
| `CompletionResponse` | Ozow sends | Every step is done, with the final status |
| `ErrorResponse` | Ozow sends | A step failed, with a flag saying whether the transaction survives it |

### Connect

The `requiredActionOptions` entry whose `action` is `websocket` carries the `uri` to connect to:

```json
{
  "requiredActionOptions": [
    {
      "action": "websocket",
      "uri": "WEBSOCKET_URI_RETURNED_FOR_THIS_TRANSACTION"
    }
  ]
}
```

Connect to the `uri` exactly as it was returned. It is issued for that one transaction, so build
nothing from its shape and store nothing from it between transactions.

Send your access token as an `Authorization` header on the opening handshake:

```http
Authorization: Bearer YOUR_ACCESS_TOKEN
```

If authentication or authorisation fails, the connection closes with `403 Forbidden`.

### Ozow asks for the next input

As soon as the connection is established, Ozow sends an `InputResponse`. It arrives again after each
step you complete.

```json
{
  "transactionId": "5ecaafc2-354f-47d9-822d-d0bd8b77eab3",
  "paymentStep": "Login",
  "pageTitle": "Login Details",
  "pageInstructions": "Log in using your internet banking profile.",
  "pageActionText": "Login",
  "displayType": "form",
  "inputFields": [
    {
      "name": "MobileNumber",
      "label": "Enter Mobile Number",
      "fieldType": "text",
      "properties": {
        "maxLength": 10,
        "isOptional": false,
        "isSensitive": false
      }
    }
  ]
}
```

`pageTitle`, `pageInstructions` and `pageActionText` are written for the payer, so you can show them
as they arrive rather than writing your own copy for a step you have never seen.

> 🚨 **These fields are where the payer types their banking credentials.** A field carrying
> `isSensitive` is one of them, and the step above asks for an internet banking login. Everything
> your interface does with these values is inside your
> [PCI DSS scope](#pci-dss-certification-mandatory): do not log them, do not persist them, and send
> them to Ozow and nowhere else.

**`displayType` is the branch your loop turns on:**

| `displayType` | What to render |
|---|---|
| `form` | Collect the fields in `inputFields` |
| `prompt` | Tell the payer to perform an action, such as accepting a push message |
| `busy` | Ask the payer to wait |

Only a `form` carries `inputFields`.

### Send the completed input

Send an `InputRequest` back with the values the payer entered:

```json
{
  "paymentStep": "Login",
  "inputFields": [
    {
      "name": "MobileNumber",
      "value": "0821234567"
    }
  ]
}
```

> 🚨 **`paymentStep` and every field `name` must match the message Ozow sent, exactly**, including
> the case. A message whose values differ is not read. Echo the strings you received rather than
> retyping them, and the case cannot drift.

Then wait for the next message. It is another `InputResponse` if the bank has more to ask, a
`CompletionResponse` if it does not, or an `ErrorResponse` if the step failed.

### Ozow reports the transaction complete

```json
{
  "transactionId": "5ecaafc2-354f-47d9-822d-d0bd8b77eab3",
  "status": "Successful"
}
```

**A completion message is not the same as a successful payment.** It means every step has been
processed, and `status` says how it ended. Read `status` rather than treating the arrival of the
message as the outcome.

### When a step fails

```json
{
  "title": "Invalid credentials",
  "detail": "The username or password was not accepted by the bank.",
  "canContinue": true
}
```

`canContinue` decides what happens next:

| `canContinue` | What to do |
|---|---|
| `true` | The transaction survives. Send the last message again with the problem corrected |
| `false` | The transaction is over |

`title` and `detail` describe what went wrong.

### The full contract

Every message, field and enumerated value is in the
[One API WebSocket reference](https://hub.ozow.com/api-reference/one-api-websocket.md). This page covers the order they
arrive in; that page covers what each one contains.

## Register your interest

If you are planning server to server payments and want to be notified when it becomes available, or
if you want to discuss your PCI DSS certification requirements with Ozow, contact
[support@ozow.com](mailto:support@ozow.com) or reach out to your account manager.