# Quick start: accept your first payment

> Accept your first Ozow payment with One API. Get a token, create a payment request, redirect the customer, and read the webhook that confirms it.

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

This guide walks you through accepting your first payment using Ozow. By the end you'll
have a working payment flow that creates a payment request and redirects your customer to complete
their payment.

> ℹ️ **Note**: If you're looking for a no-code or plugin option, head to [Integration
> methods](https://hub.ozow.com/integration-methods.md). This guide is for developers building
> an API integration.

## Before you start

Make sure you have the following in place before continuing:

- An active Ozow merchant account
- Your Client ID and Client Secret from the One API Clients section of the [Ozow Dashboard](https://dash.ozow.com)
- Your site code from the Site section of the Dashboard

If you haven't completed these steps yet, see [Prerequisites and
onboarding](https://hub.ozow.com/getting-started/prerequisites-and-onboarding.md) first.

## The scenario

The examples on this page use one ecommerce checkout:

> Fynbos Supply Co. runs an online store. A customer has added a product to their cart and is ready
> to check out. The total order value is R100. Fynbos Supply Co. wants to redirect the customer to
> Ozow to complete the payment securely.

## Step 1: Obtain an access token

One API uses OAuth 2.0 authentication. Before you can make any API calls you need to request an
access token using your Client ID and Client Secret.

Send a POST request to the token endpoint:

```endpoint
POST https://one.ozow.com/v1/token
Content-Type: application/x-www-form-urlencoded
```

**cURL**

```bash
curl -X POST "https://one.ozow.com/v1/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "scope=payments" \
  -d "grant_type=client_credentials"
```

**C#**

```csharp
var client = new HttpClient();
var content = new FormUrlEncodedContent(
    new[]
    {
        new KeyValuePair<string, string>("client_id", "YOUR_CLIENT_ID"),
        new KeyValuePair<string, string>("client_secret", "YOUR_CLIENT_SECRET"),
        new KeyValuePair<string, string>("scope", "payments"),
        new KeyValuePair<string, string>("grant_type", "client_credentials"),
    }
);
var response = await client.PostAsync("https://one.ozow.com/v1/token", content);
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
```

**PHP**

```php
<?php
$curl = curl_init();
curl_setopt_array($curl, [
    CURLOPT_URL => "https://one.ozow.com/v1/token",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        "client_id" => "YOUR_CLIENT_ID",
        "client_secret" => "YOUR_CLIENT_SECRET",
        "scope" => "payments",
        "grant_type" => "client_credentials",
    ]),
    CURLOPT_HTTPHEADER => ["Content-Type: application/x-www-form-urlencoded"],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
```

**JavaScript**

```javascript
const response = await fetch("https://one.ozow.com/v1/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    client_id: "YOUR_CLIENT_ID",
    client_secret: "YOUR_CLIENT_SECRET",
    scope: "payments",
    grant_type: "client_credentials",
  }),
});
const data = await response.json();
console.log(data);
```

**Python**

```python
import requests

response = requests.post(
    "https://one.ozow.com/v1/token",
    data={
        "client_id": "YOUR_CLIENT_ID",
        "client_secret": "YOUR_CLIENT_SECRET",
        "scope": "payments",
        "grant_type": "client_credentials",
    },
)
print(response.json())
```

**Successful response**

```json
{
  "access_token": "eyJhbGciOiJSUzI1NiJ9...",
  "token_type": "Bearer",
  "expires_in": "14400",
  "scope": "payments"
}
```

Store the `access_token`, you'll need it in the next step. Tokens expire after the number of seconds
in `expires_in`, which is 14400, four hours. Read that field rather than hard coding the number: a
change to the lifetime reaches you in the response before it reaches this page.

## Step 2: Create a payment request

Now that you have an access token, create a payment request for your customer's R100 order.

```endpoint
POST https://one.ozow.com/v1/payments
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
```

**cURL**

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

**C#**

```csharp
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer",
    "YOUR_ACCESS_TOKEN"
);

var payload = new
{
    siteCode = "YOUR_SITE_CODE",
    amount = new { currency = "ZAR", value = 100.00 },
    merchantReference = "ORDER-001",
    beneficiaryReference = "ONLINESHOP002",
    expireAt = "2026-12-31T23:59:59Z",
    returnUrl = "https://yourstore.com/order-complete",
};

var response = await client.PostAsync(
    "https://one.ozow.com/v1/payments",
    new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
);
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
```

**PHP**

```php
<?php
$curl = curl_init();
curl_setopt_array($curl, [
    CURLOPT_URL => "https://one.ozow.com/v1/payments",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
        "siteCode" => "YOUR_SITE_CODE",
        "amount" => ["currency" => "ZAR", "value" => 100.00],
        "merchantReference" => "ORDER-001",
        "beneficiaryReference" => "ONLINESHOP002",
        "expireAt" => "2026-12-31T23:59:59Z",
        "returnUrl" => "https://yourstore.com/order-complete",
    ]),
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer YOUR_ACCESS_TOKEN",
        "Content-Type: application/json",
    ],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
```

**JavaScript**

```javascript
const response = await fetch("https://one.ozow.com/v1/payments", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_ACCESS_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    siteCode: "YOUR_SITE_CODE",
    amount: { currency: "ZAR", value: 100.00 },
    merchantReference: "ORDER-001",
    beneficiaryReference: "ONLINESHOP002",
    expireAt: "2026-12-31T23:59:59Z",
    returnUrl: "https://yourstore.com/order-complete",
  }),
});
const data = await response.json();
console.log(data);
```

**Python**

```python
import requests

response = requests.post(
    "https://one.ozow.com/v1/payments",
    headers={
        "Authorization": "Bearer YOUR_ACCESS_TOKEN",
        "Content-Type": "application/json",
    },
    json={
        "siteCode": "YOUR_SITE_CODE",
        "amount": {"currency": "ZAR", "value": 100.00},
        "merchantReference": "ORDER-001",
        "beneficiaryReference": "ONLINESHOP002",
        "expireAt": "2026-12-31T23:59:59Z",
        "returnUrl": "https://yourstore.com/order-complete",
    },
)
print(response.json())
```

**Successful response**

```json
{
  "links": {
    "self": "https://one.ozow.com/v1/payments/497f6eca-6276-4993-bfeb-53cbbbba6f08",
    "transactions": "https://one.ozow.com/v1/payments/497f6eca-6276-4993-bfeb-53cbbbba6f08/transactions"
  },
  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  "status": "Created",
  "redirectUrl": "https://pay.ozow.com/497f6eca-6276-4993-bfeb-53cbbbba6f08/secure"
}
```

## Step 3: Redirect your customer

Take the `redirectUrl` from the response and redirect your customer's browser to it. Ozow will
handle the payment experience from here, your customer selects their preferred payment method and
completes the payment on Ozow's secure hosted page.

By default, Pay by Bank is available on your payment page from the moment your account is active.
Additional payment methods; such as Card, Buy Now Pay Later, Crypto, and PayShap; are enabled by the
Ozow team on request. Once activated, they appear automatically on your payment page with no
additional integration work required on your side.

Once the payment is complete, Ozow will redirect your customer back to the `returnUrl` you specified
in the payment request.

> ⚠️ **Important**: Do not use the redirect response alone to confirm payment status. Always verify
> the outcome using a webhook or by checking the transaction status via the API. See Step 4.

> ⚠️ **Important**: `returnUrl`, `notifyUrl` and a webhook URL must all be reachable from the
> internet. `localhost` is rejected with a `403`, so a tunnel to your machine is what a local
> integration needs rather than the address your browser uses.

## Step 4: Handle the outcome

Ozow notifies you of the payment outcome in three ways:

**Webhook notification**: Ozow sends an HTTP POST to your designated webhook URL when the
transaction completes. This is the recommended way to confirm payment status.

Set up your webhook endpoint in one of two ways:

- **Via the Ozow Dashboard**: navigate to [One API
  Clients](https://dash.ozow.com/MerchantAdmin/OneAPI/Clients), select your client, and manage
  webhooks from there
- **Via the API**: see the [webhook endpoints](https://hub.ozow.com/api-reference/one-api/tags/webhooks.md)

Every webhook notification includes Svix signature headers. Always verify the signature before
acting on any notification:

| Header | Description |
|---|---|
| `svix-id` | Unique message identifier, the same if the webhook is resent after a failure |
| `svix-timestamp` | Timestamp in seconds since epoch |
| `svix-signature` | Base64 encoded signature |

[Verify a webhook signature](https://hub.ozow.com/integration-methods/apis/payin/verify-a-webhook.md) has the five
steps and a working verifier in four languages, with or without the Svix library. Retrieve your
webhook secret with [Get Webhook Secret](https://hub.ozow.com/api-reference/one-api/get-webhooks-id-secret.md).

> ⚠️ **Important**: Never process a webhook without first verifying its signature. Do not rely on
> the redirect response alone to confirm payment status.

For full webhook implementation details see [Redirect to Ozow](https://hub.ozow.com/integration-methods/apis/payin/redirect-to-ozow.md#step-4-handle-the-webhook-notification).

**`notifyUrl`**: set `notifyUrl` on the payment request and Ozow also sends the standard Ozow
notification, with the same payload and the same hash the Payments API sends on a payin. It is
optional and fires only for payments where you set it, so it is there for an integration that
already has a Payments API notification handler working. Point it at that handler and nothing about
it has to change.

> ⚠️ **Important**: If you set `notifyUrl` **and** configure a webhook, both fire for the same
> payment. Make your handlers idempotent, keyed on the merchant reference or the transaction ID, or
> one payment marks an order paid twice.

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

```endpoint
GET https://one.ozow.com/v1/payments/{id}/transactions
Authorization: Bearer YOUR_ACCESS_TOKEN
```

A completed payment returns a transaction whose `status` is `Successful`. The full set is
`Incomplete`, `Successful`, `Error`, `Pending` and `Refunded`, which is the transaction's own status
rather than the four a webhook maps to; [Transaction and settlement
statuses](https://hub.ozow.com/integration-methods/statuses.md) covers the difference. Compare the value without
case.

An id that matches no payment answers `200` with an empty result list, not a `404`. Check whether
you got a transaction back rather than relying on the status code.

## What's next?

You've accepted your first payment. Here's where to go from here:

- **Set up webhooks**: [Redirect: One API](https://hub.ozow.com/integration-methods/apis/payin/redirect-to-ozow.md)
  covers webhook setup and verification in full
- **Handle edge cases**: learn how to handle failed payments, cancellations, and timeouts
- **Explore other integration paths**: [Integration
  methods](https://hub.ozow.com/integration-methods.md) covers embedded, direct, and no-code
  options
- **Go live**: review the [Building a secure integration](https://hub.ozow.com/getting-started/building-a-secure-integration.md)
  checklist before switching to your production credentials