# Integration guide (/docs/payment-orchestration/integration-guide)

## Introduction [#introduction]

This comprehensive guide covers all payment scenarios, API endpoints, and integration patterns for the Precium Payment API.

## Authentication [#authentication]

All API requests require Bearer token authentication.

**Request Format**

```bash
curl -X GET "https://gate.reviopay.com/api/v1/endpoint/" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json"
```

**API Key Types**

| Key Type | Usage |
|---|---|
| **Standard API Key** | All non-S2S operations |
| **S2S API Key** | Server-to-server card operations (requires SAQ-D) |

## Client Management [#client-management]

Clients represent your customers. Creating client records enables stored payment methods, pre-filled checkouts, and recurring billing.

**Create Client**

```json
POST https://gate.reviopay.com/api/v1/clients/
```

**Request Body:**

```json
{
  "email": "customer@example.com",
  "phone": "+27821234567",
  "full_name": "John Doe",
  "street_address": "123 Main Street",
  "city": "Cape Town",
  "state": "Western Cape",
  "zip_code": "8001",
  "country": "ZA",
  "shipping_street_address": "123 Main Street",
  "shipping_city": "Cape Town",
  "shipping_state": "Western Cape",
  "shipping_zip_code": "8001",
  "shipping_country": "ZA"
}
```

**Parameters:**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `email` | string | **Yes** | Customer email address |
| `phone` | string | No | Phone number (E.164 format) |
| `full_name` | string | No | Customer full name |
| `personal_code` | string | No | ID number (for DebiCheck) |
| `street_address` | string | No | Billing address |
| `city` | string | No | Billing city |
| `state` | string | No | Billing state/province |
| `zip_code` | string | No | Billing postal code |
| `country` | string | No | Billing country (ISO 3166-1 alpha-2) |
| `bank_account` | string | No | Bank account number |
| `bank_code` | string | No | Bank branch/SWIFT code |
| `cc` | array | No | CC email addresses for receipts |
| `bcc` | array | No | BCC email addresses |

**Response:**

```json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "client",
  "created_on": 1619740800,
  "updated_on": 1619740800,
  "email": "customer@example.com",
  "phone": "+27821234567",
  "full_name": "John Doe"
}
```

**List Clients**

```json
GET https://gate.reviopay.com/api/v1/clients/
```

**Query Parameters:**

| Parameter | Description |
|---|---|
| `q` | Fuzzy search across email, phone, name, etc. |
| `email` | Search by email |
| `phone` | Search by phone |

**Get Client**

```bash
GET https://gate.reviopay.com/api/v1/clients/{client_id}/
```

**Update Client**

```bash
PUT https://gate.reviopay.com/api/v1/clients/{client_id}/
```

**Partial Update Client**

```bash
PATCH https://gate.reviopay.com/api/v1/clients/{client_id}/
```

**Delete Client**

```bash
DELETE https://gate.reviopay.com/api/v1/clients/{client_id}/
```

**List Client Recurring Tokens**

Retrieve stored payment methods for a client:

```bash
GET https://gate.reviopay.com/api/v1/clients/{client_id}/recurring_tokens/
```

**Response:**

```json
{
  "results": [
    {
      "id": "tok_abc123...",
      "type": "recurring_token",
      "payment_method": "visa",
      "description": "Visa ending in 1111",
      "created_on": 1619740800
    }
  ]
}
```

**Delete Client Token**

```bash
DELETE https://gate.reviopay.com/api/v1/clients/{client_id}/recurring_tokens/{token_id}/
```

## Purchase Management [#purchase-management]

A Purchase represents a payment request. Create a purchase to generate a checkout URL or process a payment.

**Create Purchase**

```bash
POST https://gate.reviopay.com/api/v1/purchases/
```

**Full Request Body:**

```json
{
  "client_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "purchase": {
    "currency": "ZAR",
    "language": "en",
    "products": [
      {
        "name": "Product Name",
        "price": 10000,
        "quantity": "1",
        "discount": 0,
        "tax_percent": "15",
        "category": "electronics"
      }
    ],
    "notes": "Special instructions",
    "due_strict": false
  },
  "brand_id": "YOUR_BRAND_ID",
  "reference": "INV-2026-0001",
  "payment_method_whitelist": ["visa", "mastercard", "ozow"],
  "success_redirect": "https://www.example.com/success",
  "failure_redirect": "https://www.example.com/failure",
  "cancel_redirect": "https://www.example.com/cancel",
  "success_callback": "https://www.example.com/callback",
  "send_receipt": true,
  "force_recurring": false,
  "skip_capture": false,
  "due": 1735689600
}
```

**Core Parameters:**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `brand_id` | uuid | **Yes** | Your brand identifier |
| `purchase.currency` | string | **Yes** | ISO 4217 currency code |
| `purchase.products` | array | **Yes** | Array of product objects |
| `purchase.products[].name` | string | **Yes** | Product name |
| `purchase.products[].price` | integer | **Yes** | Price in cents |

**Client Parameters (use `client_id` OR `client` object):**

| Parameter | Type | Description |
|---|---|---|
| `client_id` | uuid | Existing client ID |
| `client.email` | string | Create/find client by email |
| `client.phone` | string | Client phone |
| `client.full_name` | string | Client name |

**Optional Parameters:**

| Parameter | Type | Default | Description |
|---|---|---|---|
| `reference` | string | --- | Your order reference |
| `payment_method_whitelist` | array | all | Allowed payment methods |
| `success_redirect` | string | --- | Redirect URL on success |
| `failure_redirect` | string | --- | Redirect URL on failure |
| `cancel_redirect` | string | --- | Redirect URL on cancel |
| `success_callback` | string | --- | Webhook URL for this purchase |
| `send_receipt` | boolean | false | Email receipt on payment |
| `force_recurring` | boolean | false | Force token storage |
| `skip_capture` | boolean | false | Pre-authorization only |
| `due` | integer | --- | Due date (Unix timestamp) |
| `purchase.due_strict` | boolean | false | Block payment after due date |
| `purchase.language` | string | en | Checkout language |

**Response:**

```json
{
  "id": "pur_abc123...",
  "type": "purchase",
  "status": "created",
  "created_on": 1619740800,
  "checkout_url": "https://gate.reviopay.com/p/abc123.../",
  "direct_post_url": "https://gate.reviopay.com/p/abc123.../post/",
  "purchase": {
    "currency": "ZAR",
    "total": 10000,
    "products": [...]
  },
  "brand_id": "YOUR_BRAND_ID",
  "reference": "INV-2026-0001"
}
```

**Get Purchase**

```bash
GET https://gate.reviopay.com/api/v1/purchases/{purchase_id}/
```

**List purchases for a client**

There is no endpoint that lists every purchase. Purchases are listed per client,
through the client's feed, which returns that client's purchases and refunds
together.

```bash
GET https://gate.reviopay.com/api/v1/clients/{client_id}/feed/
```

<Callout type="warning">
`GET /api/v1/purchases/` does not exist. `/purchases/` accepts `POST` only.
Retrieving a single purchase is
`GET /api/v1/purchases/{purchase_id}/`, which is a different thing from
listing them.

Hold your own index of purchase ids against your orders. Reconciliation matches
on `gateway_transaction_id`, which is that same id, so you need it stored on
your side regardless. See [Reconciliation files](/docs/reconciliation).
</Callout>

**Charge Purchase**

Charge a purchase using a recurring token:

```bash
POST https://gate.reviopay.com/api/v1/purchases/{purchase_id}/charge/
```

**Request Body:**

```json
{
  "recurring_token": "tok_abc123..."
}
```

**Capture Pre-Authorization**

Capture funds from a pre-authorized purchase:

```bash
POST https://gate.reviopay.com/api/v1/purchases/{purchase_id}/capture/
```

**Request Body:**

```json
{
  "amount": 10000
}
```

**Release Pre-Authorization**

Release held funds without capturing:

```bash
POST https://gate.reviopay.com/api/v1/purchases/{purchase_id}/release/
```

**Cancel Purchase**

```bash
POST https://gate.reviopay.com/api/v1/purchases/{purchase_id}/cancel/
```

**Resend Invoice**

```bash
POST https://gate.reviopay.com/api/v1/purchases/{purchase_id}/resend_invoice/
```

**Mark as Paid**

Manually mark a purchase as paid (for offline payments):

```bash
POST https://gate.reviopay.com/api/v1/purchases/{purchase_id}/mark_as_paid/
```

## Payment Scenarios [#payment-scenarios]

### Scenario 1: Payment Link (Minimal Integration) [#scenario-1-payment-link-minimal-integration]

Generate a payment link to send via email or messaging:

```bash
POST https://gate.reviopay.com/api/v1/purchases/
```

```json
{
  "client": {
    "email": "customer@example.com"
  },
  "purchase": {
    "currency": "ZAR",
    "products": [
      {
        "name": "Invoice #12345",
        "price": 150000
      }
    ]
  },
  "brand_id": "YOUR_BRAND_ID"
}
```

Share the `checkout_url` from the response with your customer.

### Scenario 2: E-commerce Checkout (Redirect Flow) [#scenario-2-e-commerce-checkout-redirect-flow]

Standard e-commerce integration with redirect:

```mermaid
sequenceDiagram
    participant C as Customer
    participant M as Your Site
    participant P as Precium

    C->>M: Click "Pay Now"
    M->>P: POST /purchases/
    P-->>M: checkout_url
    M->>C: Redirect to checkout_url
    C->>P: Complete payment
    P->>C: Redirect to success_redirect
    P->>M: Webhook: purchase.paid
    M->>C: Show confirmation
```

**Create Purchase:**

```json
{
  "client_id": "existing_client_id",
  "purchase": {
    "currency": "ZAR",
    "products": [
      {"name": "Widget", "price": 5000, "quantity": "2"},
      {"name": "Shipping", "price": 5000}
    ]
  },
  "brand_id": "YOUR_BRAND_ID",
  "reference": "ORD-12345",
  "payment_method_whitelist": ["visa", "mastercard", "ozow"],
  "success_redirect": "https://www.example.com/order/ORD-12345/success",
  "failure_redirect": "https://www.example.com/order/ORD-12345/failure",
  "cancel_redirect": "https://www.example.com/cart"
}
```

### Scenario 3: Pre-Authorization (Hold Funds) [#scenario-3-pre-authorization-hold-funds]

Reserve funds for later capture (hotels, car rentals):

**Step 1: Create Pre-Auth Purchase**

```json
{
  "client_id": "client_uuid",
  "purchase": {
    "currency": "ZAR",
    "products": [
      {"name": "Hotel Deposit", "price": 500000}
    ]
  },
  "brand_id": "YOUR_BRAND_ID",
  "skip_capture": true,
  "success_redirect": "https://www.example.com/booking/success",
  "failure_redirect": "https://www.example.com/booking/failure"
}
```

**Step 2: Capture When Ready**

```bash
POST https://gate.reviopay.com/api/v1/purchases/{purchase_id}/capture/
```

```json
{
  "amount": 450000
}
```

**Or Release if Not Needed:**

```bash
POST https://gate.reviopay.com/api/v1/purchases/{purchase_id}/release/
```

### Scenario 4: Restrict Payment Methods [#scenario-4-restrict-payment-methods]

Only allow specific payment methods:

```json
{
  "payment_method_whitelist": ["visa", "mastercard"],
  ...
}
```

**Available Payment Methods:**

| Value | Description |
|---|---|
| `visa` | Visa cards |
| `mastercard` | Mastercard |
| `maestro` | Maestro cards |
| `ozow` | Ozow Instant EFT |
| `capitec_pay` | Capitec Pay |
| `capitec_pay_recurring` | Capitec Pay Recurring |
| `zapper` | Zapper |
| `debicheck` | DebiCheck |
| `eft` | Traditional debit order / EFT collection |
| `debicheck` | Authenticated debit order (DebiCheck) |

### Scenario 5: Invoice with Due Date [#scenario-5-invoice-with-due-date]

Create an invoice that's due by a specific date:

```json
{
  "client_id": "client_uuid",
  "purchase": {
    "currency": "ZAR",
    "due_strict": true,
    "products": [
      {"name": "Consulting Services", "price": 2500000}
    ]
  },
  "brand_id": "YOUR_BRAND_ID",
  "due": 1735689600,
  "reference": "INV-2026-0042"
}
```

Setting `due_strict: true` prevents payment after the due date.

## Hosted Checkout [#hosted-checkout]

The hosted checkout page is a PCI-compliant payment form hosted by Precium.

### Checkout URL [#checkout-url]

After creating a purchase, redirect customers to the `checkout_url`:

```text
https://gate.reviopay.com/p/{purchase_id}/
```

### Customization [#customization]

Checkout appearance is configured via your Brand settings in the dashboard:

- Logo
- Colors
- Business name
- Support contact

### Checkout Flow [#checkout-flow]

```mermaid
flowchart TD
    A[Customer arrives at checkout_url] --> B{Multiple payment methods?}
    B -->|Yes| C[Select payment method]
    B -->|No| D[Show payment form]
    C --> D
    D --> E[Enter payment details]
    E --> F{3DS Required?}
    F -->|Yes| G[Complete 3DS]
    F -->|No| H[Process payment]
    G --> H
    H --> I{Success?}
    I -->|Yes| J[Redirect to success_redirect]
    I -->|No| K[Redirect to failure_redirect]
```

## Direct Post Integration [#direct-post-integration]

Direct Post allows you to create a custom checkout form while keeping card data out of your servers (SAQ A-EP compliance).

### How It Works [#how-it-works]

1. Create a purchase to get a `direct_post_url`
2. Build an HTML form that POSTs directly to the `direct_post_url`
3. Card data goes directly from the browser to Precium
4. Customer is redirected based on the result

### PCI Compliance Note [#pci-compliance-note]

<Callout type="info">
Important: Direct Post integration requires PCI DSS SAQ A-EP compliance. Contact your account manager before implementing.
</Callout>

### Form Structure [#form-structure]

```html
<form method='POST' action='{direct_post_url}'>
  <input type='hidden' name='pm' value='visa'>

  <input type='text' name='cardholder_name'
         maxlength='30' required>

  <input type='text' name='card_number'
         maxlength='19' required>

  <input type='text' name='expires'
         placeholder='MM/YY' maxlength='5' required>

  <input type='text' name='cvc'
         maxlength='4' required>

  <input type='checkbox' name='remember_card' value='on'>
  <label>Save card for future payments</label>

  <button type='submit'>Pay Now</button>
</form>
```

### Field Requirements [#field-requirements]

| Field | Format | Validation |
|---|---|---|
| `pm` | string | Payment method: `visa`, `mastercard`, etc. |
| `cardholder_name` | string | Latin letters, space, apostrophe, dot, dash. Max 30 chars |
| `card_number` | string | Digits only, no spaces. Max 19 chars |
| `expires` | string | `MM/YY` format |
| `cvc` | string | 3-4 digits |
| `remember_card` | string | `on` to save card |

### Direct Post for Other Payment Methods [#direct-post-for-other-payment-methods]

<Tabs items={['Capitec Pay', 'DebiCheck', 'Ozow']}>
<Tab value="Capitec Pay">

```html
<form method='POST' action='{direct_post_url}'>
  <input type='hidden' name='pm' value='capitec_pay'>
  <input type='hidden' name='capitec_radio' value='mobile_number'>
  <input type='text' name='capitec_mobile_number' required>
  <input type='hidden' name='accepted_terms' value='on'>
  <button type='submit'>Pay with Capitec Pay</button>
</form>
```

</Tab>
<Tab value="DebiCheck">

```html
<form method='POST' action='https://gate.reviopay.com/p/{purchase_id}/'>
  <input type='hidden' name='pm' value='debicheck'>
  <input type='text' name='PayerFirstName' required>
  <input type='text' name='PayerLastName' required>
  <input type='text' name='PayerPhoneNumber' required>
  <input type='hidden' name='PayerIdType' value='idNumber'>
  <input type='text' name='PayerdNumber' required>
  <input type='text' name='PayerBranchCode' maxlength='6' required>
  <input type='text' name='PayerAccountType' value='Current'>
  <input type='text' name='PayerAccount' required>
  <button type='submit'>Set Up DebiCheck</button>
</form>
```

</Tab>
<Tab value="Ozow">

```json
s2s:true
pm:ozow
phone:27123456789
```

</Tab>
</Tabs>


## Recurring Payments [#recurring-payments]

### Tokenizing Payment Methods [#tokenizing-payment-methods]

To save a payment method for future charges, set `force_recurring: true`:

```json
{
  "client_id": "client_uuid",
  "purchase": {
    "currency": "ZAR",
    "products": [
      {"name": "Initial Payment", "price": 10000}
    ]
  },
  "brand_id": "YOUR_BRAND_ID",
  "force_recurring": true,
  "success_redirect": "https://www.example.com/success"
}
```

After successful payment, the purchase ID becomes a recurring token.

### Zero-Amount Tokenization [#zero-amount-tokenization]

To tokenize a card without charging:

```json
{
  "client_id": "client_uuid",
  "purchase": {
    "currency": "ZAR",
    "products": [
      {"name": "Card Authorization", "price": 0}
    ]
  },
  "brand_id": "YOUR_BRAND_ID",
  "skip_capture": true,
  "force_recurring": true,
  "success_redirect": "https://www.example.com/success"
}
```

### Charging with Recurring Token [#charging-with-recurring-token]

**Step 1: Create Purchase for Upcoming Charge**

```bash
POST https://gate.reviopay.com/api/v1/purchases/
```

```json
{
  "client_id": "client_uuid",
  "purchase": {
    "currency": "ZAR",
    "products": [
      {"name": "Monthly Subscription", "price": 29900}
    ]
  },
  "brand_id": "YOUR_BRAND_ID",
  "reference": "SUB-2026-01"
}
```

**Step 2: Charge Using Token**

```bash
POST https://gate.reviopay.com/api/v1/purchases/{new_purchase_id}/charge/
```

```json
{
  "recurring_token": "{original_purchase_id}"
}
```

### Listing Client Tokens [#listing-client-tokens]

```bash
GET https://gate.reviopay.com/api/v1/clients/{client_id}/recurring_tokens/
```

## Billing Templates (Subscriptions) [#billing-templates-subscriptions]

<Callout type="important">
**No longer offered.** Subscription billing via billing templates is not sold to new integrations. This section is retained for existing implementations only. For recurring charges, use tokenisation and merchant-initiated charges — see *Recurring Payments* above.
</Callout>

Billing templates automate recurring billing with configurable schedules.

### Create Billing Template [#create-billing-template]

```bash
POST https://gate.reviopay.com/api/v1/billing_templates/
```

```json
{
  "title": "Premium Monthly Subscription",
  "purchase": {
    "currency": "ZAR",
    "products": [
      {
        "name": "Premium Plan",
        "price": 29900
      }
    ]
  },
  "brand_id": "YOUR_BRAND_ID",
  "is_subscription": true,
  "subscription_active": true,
  "subscription_period": 1,
  "subscription_period_units": "months",
  "number_of_billing_cycles": 12,
  "subscription_due_period": 3,
  "subscription_due_period_units": "days",
  "subscription_trial_periods": 0,
  "force_recurring": true,
  "success_redirect": "https://www.example.com/subscribe/success",
  "failure_redirect": "https://www.example.com/subscribe/failure"
}
```

**Subscription Parameters:**

| Parameter | Type | Description |
|---|---|---|
| `is_subscription` | boolean | Enable subscription mode |
| `subscription_active` | boolean | Activate automatic billing |
| `subscription_period` | integer | Billing frequency number |
| `subscription_period_units` | string | `days`, `weeks`, `months`, `years` |
| `number_of_billing_cycles` | integer | Total billing cycles (0 = infinite) |
| `subscription_due_period` | integer | Days before due date to create invoice |
| `subscription_trial_periods` | integer | Free trial periods |
| `subscription_charge_period_end` | boolean | Charge at end of period |

### Add Subscriber [#add-subscriber]

```bash
POST https://gate.reviopay.com/api/v1/billing_templates/{template_id}/add_subscriber/
```

```json
{
  "client_id": "client_uuid",
  "invoice_reference": "SUB-CUST-001",
  "subscription_confirm_each_order": false,
  "send_invoice_on_add_subscriber": false,
  "send_invoice_on_charge_failure": true,
  "send_receipt": true
}
```

**Response includes:**

- `billing_template_client.id` - Subscription ID
- `purchase.checkout_url` - URL for initial tokenization
- `purchase.direct_post_url` - Direct post URL

### Add Subscriber with Override [#add-subscriber-with-override]

Customise pricing for specific subscribers:

```json
{
  "client_id": "client_uuid",
  "invoice_reference": "SUB-VIP-001",
  "override": {
    "products": [
      {
        "name": "VIP Plan (Discounted)",
        "price": 19900
      }
    ]
  }
}
```

### List Billing Templates [#list-billing-templates]

```bash
GET https://gate.reviopay.com/api/v1/billing_templates/
```

### Get Billing Template [#get-billing-template]

```bash
GET https://gate.reviopay.com/api/v1/billing_templates/{template_id}/
```

### List Subscribers [#list-subscribers]

```bash
GET https://gate.reviopay.com/api/v1/billing_templates/{template_id}/clients/
```

### Update Subscriber [#update-subscriber]

A subscriber is updated with `PATCH`, not `PUT`. The update is partial: send
only the fields you are changing.

```bash
PATCH https://gate.reviopay.com/api/v1/billing_templates/{template_id}/clients/{subscription_id}/
```

<Callout type="warning">
There is **no operation to remove a subscriber**. The API exposes only `GET`
and `PATCH` on this path. To stop billing a subscriber, change the subscription
through `PATCH` or cancel the billing template.
</Callout>

## Refunds [#refunds]

### Full Refund [#full-refund]

```bash
POST https://gate.reviopay.com/api/v1/purchases/{purchase_id}/refund/
```

```json
{}
```

### Partial Refund [#partial-refund]

```bash
POST https://gate.reviopay.com/api/v1/purchases/{purchase_id}/refund/
```

```json
{
  "amount": 5000
}
```

### Refund Response [#refund-response]

```json
{
  "id": "ref_abc123...",
  "type": "refund",
  "status": "pending",
  "amount": 5000,
  "purchase_id": "pur_abc123..."
}
```

### Refund Limitations [#refund-limitations]

- Refunds must not exceed the original payment amount
- Partial refunds can be issued multiple times until fully refunded
- Some payment methods may not support refunds (check payment method details)

## Webhooks [#webhooks]

Webhooks notify your application of payment events in real-time.

### Available Events [#available-events]

<Callout type="warning">
Subscribe only to the events you act on. Event names are exact — a name that does not appear below will silently never fire.
</Callout>

| Event | Fires when |
|---|---|
| `purchase.created` | A purchase has been created |
| `purchase.viewed` | The payer opened the checkout |
| `purchase.paid` | Payment captured and complete |
| `purchase.payment_failure` | Payment failed |
| `purchase.cancelled` | Purchase cancelled |
| `purchase.hold` | Funds reserved by an authorisation |
| `purchase.preauthorized` | Zero-amount card verification succeeded |
| `purchase.captured` | A hold was captured |
| `purchase.capture_failure` | Capture failed |
| `purchase.released` | A hold was released without capture |
| `purchase.release_failure` | Release failed |
| `purchase.pending_charge` | Authorisation in flight at the acquirer |
| `purchase.pending_capture` | Capture accepted, still processing |
| `purchase.pending_release` | Release accepted, still processing |
| `purchase.pending_refund` | Refund accepted, still processing |
| `payment.refunded` | A refund completed. Treat this as the completion signal |
| `purchase.refund_failure` | Refund failed after acceptance |
| `payment.charged_back` | A chargeback was raised |
| `payment.chargeback_reversed` | A chargeback was reversed in your favour |
| `purchase.settled` | The transaction settled |
| `purchase.recurring_token_deleted` | A stored card token was deleted |

<Callout type="warning">
Note that `purchase.pending`, `purchase.authorized`, `purchase.refunded`, `subscription.created` and `subscription.cancelled` are **not** valid event names. Use `purchase.pending_charge`, `purchase.preauthorized` and `payment.refunded` instead.
</Callout>

### Create Webhook [#create-webhook]

```bash
POST https://gate.reviopay.com/api/v1/webhooks/
```

```json
{
  "url": "https://www.example.com/webhooks/precium",
  "events": ["purchase.paid", "purchase.payment_failure", "payment.refunded"]
}
```

### Webhook Payload [#webhook-payload]

```json
{
  "id": "evt_abc123...",
  "event_type": "purchase.paid",
  "created_on": 1619740800,
  "data": {
    "id": "pur_abc123...",
    "status": "paid",
    "purchase": {
      "total": 10000,
      "currency": "ZAR"
    },
    "reference": "ORD-12345",
    "payment_method_details": {
      "card": {
        "brand": "visa",
        "last4": "1111"
      }
    }
  }
}
```

### Webhook Security [#webhook-security]

Verify webhook authenticity using the signature header:

```python
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding

# Callbacks are signed with ASYMMETRIC RSA. There is no shared secret.
# X-Signature = base64( RSA PKCS#1 v1.5 over the SHA-256 digest of the raw body )
#   webhook subscriptions -> public_key on the webhook object
#   per-purchase success_callback -> GET /public_key/

def verify_webhook(raw_body: bytes, signature_b64: str, public_key_pem: str) -> bool:
    key = serialization.load_pem_public_key(public_key_pem.encode())
    try:
        key.verify(
            base64.b64decode(signature_b64),
            raw_body,                 # raw bytes; never a re-serialised dict
            padding.PKCS1v15(),
            hashes.SHA256(),
        )
        return True
    except Exception:
        return False
```

### Webhook Best Practices [#webhook-best-practices]

1. **Respond quickly** — Return 200 within 5 seconds
2. **Process asynchronously** — Queue for background processing
3. **Handle duplicates** — Use event ID for idempotency
4. **Verify signatures** — Validate webhook authenticity

## Payouts [#payouts]

<Callout type="warning">
**Not currently available.** Payouts are not yet released. The endpoints are documented here for reference only — do not build against them, and speak to your account manager before planning a disbursement flow.
</Callout>

Send funds to recipients using stored payment methods.

### Create Payout [#create-payout]

```bash
POST https://gate.reviopay.com/api/v1/payouts/
```

```json
{
  "client": {
    "email": "recipient@example.com",
    "phone": "+27821234567"
  },
  "payment": {
    "amount": 50000,
    "currency": "ZAR",
    "description": "Commission Payment"
  },
  "brand_id": "YOUR_BRAND_ID"
}
```

### Execute Payout [#execute-payout]

```bash
POST {execution_url}
```

```json
{
  "cardholder_name": "John Doe",
  "card_number": "4444333322221111",
  "expiry_month": "12",
  "expiry_year": "28"
}
```

### Get Payout Status [#get-payout-status]

```bash
GET https://gate.reviopay.com/api/v1/payouts/{payout_id}/
```

## API Response Codes [#api-response-codes]

| Code | Description |
|---|---|
| `200` | Success |
| `201` | Created |
| `204` | No content (successful deletion) |
| `400` | Bad request (validation error) |
| `401` | Unauthorized (invalid API key) |
| `404` | Not found |
| `409` | Conflict (duplicate request) |
| `429` | Rate limited |
| `500` | Server error |

## Rate Limiting [#rate-limiting]

API requests are rate-limited to ensure fair usage:

- **Standard:** 10 transactions per second, per IP address
- **Burst:** 200 requests per minute (short periods)

Rate limit headers:

```text
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1619740860
```

## Pagination [#pagination]

List endpoints use cursor-based pagination:

```json
{
  "results": [...],
  "next": "https://gate.reviopay.com/api/v1/purchases/?cursor=abc123",
  "previous": null
}
```

Follow the `next` URL to retrieve additional pages.
