---
title: Integration Guide
description: "This document explains how to integrate with the **RWDPay Payment Gateway**, including environments, integration flows, request/response fields, signature rules, and PHP implementation examples."
---

# RWDPay Payment Gateway API — Integration Guide

This document explains how to integrate with the **RWDPay Payment Gateway**, including environments, integration flows, request/response fields, signature rules, and PHP implementation examples.

---

## 1. Endpoints

| Description     | Environment | Base URL                                                                  |
| --------------- | ----------- | ------------------------------------------------------------------------- |
| Merchant Portal | UAT         | `https://portal-uat.rwdpay.com/login`                                     |
| Merchant Portal | Production  | `https://portal.rwdpay.com/login`                                         |
| API             | UAT         | `https://uat.rwdpay.com/api/v1`      |
| API             | Production  | `https://gateway.rwdpay.com/api/v1` |

> In the examples below, replace `{BASE_URL}` with your environment’s API base URL.

---

## 2. Integration flows

### 2.1. Standard hosted payment (browser form POST)

**Use this when** you want to post an HTML form to RWDPay from the shopper’s browser.

**High-level steps**

1. Your backend prepares and signs the payment fields.
2. Your frontend posts an HTML form to RWDPay.
3. RWDPay redirects the shopper to `redirect_url` and (optionally) sends a server-to-server callback to `callback_url`.

```mermaid
sequenceDiagram
  participant U as User (Merchant Front-end)
  participant S as Merchant Server
  participant R as RWDPay

  U->>S: Checkout initiated
  S->>U: Return signed form fields
  U->>R: POST /payment (HTML form)
  R->>U: Redirect to redirect_url (status payload)
  R-->>S: POST callback_url (status payload)
```

### 2.2. Payment link (server-to-server, then redirect)

**Use this when** you want your server to create a payment and receive a **payment URL**, then redirect the shopper.

**High-level steps**

1. Your backend calls the API to create a payment and receives `payment_url`.
2. Your frontend redirects the shopper to `payment_url`.
3. RWDPay redirects and calls back as in the standard flow.

```mermaid
sequenceDiagram
  participant U as User (Merchant Front-end)
  participant S as Merchant Server
  participant R as RWDPay

  U->>S: Checkout initiated
  S->>R: POST /payment (JSON)
  R->>S: payment_url returned
  S->>U: Redirect instruction (payment_url)
  U->>R: Open payment_url and complete payment
  R->>U: Redirect to redirect_url (status payload)
  R-->>S: POST callback_url (status payload)
```

### 2.3. Status inquiry (server-to-server)

**Use this when** you need to query the latest transaction status from your server (e.g., reconciliation, callback not received, or manual checks).

```mermaid
sequenceDiagram
  participant S as Merchant Server
  participant R as RWDPay

  S->>R: POST /inquiry (transaction_id or merchant_ref)
  R->>S: Status response (JSON)
```

---

## 3. Data definitions

### 3.1. Standards

| Type     | Description                                       | Example                  |
| -------- | ------------------------------------------------- | ------------------------ |
| Currency | ISO 4217 currency code (3 characters)             | `MYR`                    |
| Amount   | Minor units of the currency (e.g., cents for MYR) | `1500` (i.e., MYR 15.00) |

### 3.2. Payment status codes

| Code  | Meaning                                       |
| ----- | --------------------------------------------- |
| `000` | Payment successful                            |
| `100` | Payment initiated                             |
| `101` | Payment processing                            |
| `200` | Timeout (payment not completed before expiry) |
| `201` | User cancelled                                |
| `202` | Payment failed                                |

### 3.3. Signature

RWDPay uses **HMAC-SHA256** signatures (hex-encoded) for payment requests and redirect/callback payloads.

- **Algorithm:** HMAC-SHA256
- **Encoding:** hex
- **Key:** Secret key provided by RWDPay
- **Important:** Keep the secret key server-side only.

**PHP signing helper**

```php
<?php
function sign(string $payload, string $secret): string {
    return hash_hmac('sha256', $payload, $secret);
}
```

---

## 4. API requests

### 4.1. Create payment — standard hosted flow

The shopper’s browser submits a form POST to RWDPay.

**HTTP request**

```
POST {BASE_URL}/payment
Content-Type: application/x-www-form-urlencoded
Accept: text/html
```

**Form fields**

| Field          |    Type | Required | Description                                                                                              |
| -------------- | ------: | :------: | -------------------------------------------------------------------------------------------------------- |
| `merchant_id`  |  string |    ✓     | Merchant ID provided by RWDPay                                        |
| `merchant_ref` |  string |    ✓     | Unique reference from merchant (case-insensitive). Allowed: alphanumeric + `.`, `-`, `_`. Max 255 chars. |
| `amount`       | integer |    ✓     | Amount in minor units. See [Standards](#31-standards).                                                   |
| `currency`     |  string |    ✓     | Currency code. See [Standards](#31-standards).                                                           |
| `signature`    |  string |    ✓     | HMAC-SHA256 signature. Payload: `merchant_id + merchant_ref + amount + currency`                         |
| `redirect_url` |  string | Optional | URL to redirect the shopper to after payment completion.                                                 |
| `callback_url` |  string | Optional | Server-to-server callback URL to receive payment status.                                                 |

### 4.2. Create payment — payment link flow

Your server calls RWDPay to create a payment and receive a payment link.

**HTTP request**

```
POST {BASE_URL}/payment
Content-Type: application/json
Accept: application/json
```

**JSON body fields**

Same fields as [Create payment — standard hosted flow](#42-create-payment-payment-link-flow).

**JSON response**

```typescript
type Response = {
  payment_url: string;
};
```

### 4.3. Status inquiry

This endpoint accepts either JSON or form POST.

**HTTP request**

```
POST {BASE_URL}/inquiry
Accept: application/json
```

**Request fields**

| Field            |   Type |                 Required                  | Description                                                                   |
| ---------------- | -----: | :---------------------------------------: | ----------------------------------------------------------------------------- |
| `merchant_id`    | string |                     ✓                     | Merchant ID provided by RWDPay             |
| `transaction_id` | string |  Required when `merchant_ref` is missing  | Unique transaction ID generated by RWDPay  |
| `merchant_ref`   | string | Required when `transaction_id` is missing | Your merchant reference                                                       |
| `signature`      | string |                     ✓                     | HMAC-SHA256 signature. Payload: `merchant_id + transaction_id + merchant_ref` |

**Important behavior**

- If **both** `transaction_id` and `merchant_ref` are supplied, the pair must **exactly match**, otherwise the API returns **HTTP 404** (no record found).

**Signature payload rule**

- Use empty string for any missing field when building the payload:
  - `payload = merchant_id + (transaction_id or "") + (merchant_ref or "")`

**JSON response**

- No signature is returned because this response is returned directly from server.

```typescript
type Response = {
  transaction_id: string;
  merchant_ref: string;
  amount: number;
  currency: string;
  status: string;
};
```

---

## 5. Redirect and callback payload

After payment completion (success or failure), RWDPay will send a **POST** request to your `redirect_url` (browser) and/or `callback_url` (server-to-server), with the following fields:

| Field            |    Type | Description                                                                                                |
| ---------------- | ------: | ---------------------------------------------------------------------------------------------------------- |
| `merchant_id`    |  string | Merchant ID provided by RWDPay                                          |
| `transaction_id` |  string | Unique transaction ID generated by RWDPay                               |
| `merchant_ref`   |  string | Your merchant reference                                                                                    |
| `amount`         | integer | Amount in minor units. See [Standards](#31-standards).                                                     |
| `currency`       |  string | Currency code. See [Standards](#31-standards).                                                             |
| `status`         |  string | Status code. See [Payment status codes](#32-payment-status-codes).                                         |
| `signature`      |  string | HMAC-SHA256 signature. Payload: `merchant_id + transaction_id + merchant_ref + amount + currency + status` |

---

## 6. Implementation examples (PHP)

### 6.1. Generate and verify signatures

```php
<?php
function sign(string $payload, string $secret): string {
    return hash_hmac('sha256', $payload, $secret);
}

function timingSafeEquals(string $a, string $b): bool {
    return hash_equals($a, $b);
}
```

### 6.2. Standard hosted payment: render an auto-posting form

```php
<?php
$baseUrl    = 'https://uat.rwdpay.com/api/v1';
$secret     = getenv('RWDPAY_SECRET');
$merchantId = getenv('RWDPAY_MERCHANT_ID');

$merchantRef = 'ORDER_' . time();
$amount      = 1500; // MYR 15.00
$currency    = 'MYR';

$payload   = $merchantId . $merchantRef . $amount . $currency;
$signature = sign($payload, $secret);

$redirectUrl = 'https://merchant.example.com/rwdpay/return';
$callbackUrl = 'https://merchant.example.com/rwdpay/callback';
?>
<!doctype html>
<html>
  <head><meta charset="utf-8"><title>Redirecting…</title></head>
  <body>
    <p>Redirecting to payment page…</p>

    <form id="rwdpay" method="POST" action="<?= htmlspecialchars($baseUrl . '/payment', ENT_QUOTES) ?>">
      <input type="hidden" name="merchant_id"  value="<?= htmlspecialchars($merchantId, ENT_QUOTES) ?>">
      <input type="hidden" name="merchant_ref" value="<?= htmlspecialchars($merchantRef, ENT_QUOTES) ?>">
      <input type="hidden" name="amount"       value="<?= (int)$amount ?>">
      <input type="hidden" name="currency"     value="<?= htmlspecialchars($currency, ENT_QUOTES) ?>">
      <input type="hidden" name="signature"    value="<?= htmlspecialchars($signature, ENT_QUOTES) ?>">
      <input type="hidden" name="redirect_url" value="<?= htmlspecialchars($redirectUrl, ENT_QUOTES) ?>">
      <input type="hidden" name="callback_url" value="<?= htmlspecialchars($callbackUrl, ENT_QUOTES) ?>">
      <noscript><button type="submit">Continue</button></noscript>
    </form>

    <script>
      document.getElementById('rwdpay').submit();
    </script>
  </body>
</html>
```

### 6.3. Payment link: create link via API (cURL)

```php
<?php
$baseUrl    = 'https://uat.rwdpay.com/api/v1';
$secret     = getenv('RWDPAY_SECRET');
$merchantId = getenv('RWDPAY_MERCHANT_ID');

$merchantRef = 'ORDER_' . time();
$amount      = 1500;
$currency    = 'MYR';

$payload   = $merchantId . $merchantRef . $amount . $currency;
$signature = sign($payload, $secret);

$requestBody = [
    'merchant_id'  => $merchantId,
    'merchant_ref' => $merchantRef,
    'amount'       => $amount,
    'currency'     => $currency,
    'signature'    => $signature,
    'redirect_url' => 'https://merchant.example.com/rwdpay/return',
    'callback_url' => 'https://merchant.example.com/rwdpay/callback',
];

$ch = curl_init($baseUrl . '/payment');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'Accept: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode($requestBody, JSON_UNESCAPED_SLASHES),
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($response === false) {
    throw new RuntimeException('cURL error: ' . curl_error($ch));
}
curl_close($ch);

if ($httpCode < 200 || $httpCode >= 300) {
    throw new RuntimeException("RWDPay returned HTTP $httpCode: $response");
}

$data = json_decode($response, true);
if (!is_array($data)) {
    throw new RuntimeException('Invalid JSON response: ' . $response);
}

$paymentUrl = $data['payment_url'] ?? null;
if (!$paymentUrl) {
    throw new RuntimeException('payment_url not found in response: ' . $response);
}

header('Location: ' . $paymentUrl, true, 302);
exit;
```

### 6.4. Handle callback/redirect and verify signature

```php
<?php
$secret = getenv('RWDPAY_SECRET');

$merchantId    = $_POST['merchant_id']    ?? '';
$transactionId = $_POST['transaction_id'] ?? '';
$merchantRef   = $_POST['merchant_ref']   ?? '';
$amount        = (int)($_POST['amount']   ?? 0);
$currency      = $_POST['currency']       ?? '';
$status        = $_POST['status']         ?? '';
$receivedSig   = $_POST['signature']      ?? '';

$payload     = $merchantId . $transactionId . $merchantRef . $amount . $currency . $status;
$expectedSig = sign($payload, $secret);

if (!timingSafeEquals($expectedSig, $receivedSig)) {
    http_response_code(400);
    echo 'Invalid signature';
    exit;
}

// Idempotently update your order record using merchant_ref and/or transaction_id.
switch ($status) {
    case '000':
        // mark order paid
        break;
    case '201':
        // user cancelled
        break;
    case '202':
        // failed
        break;
    default:
        // initiated/processing/timeout etc.
        break;
}

http_response_code(200);
echo 'OK';
```

### 6.5. Status inquiry via API (cURL)

```php
<?php
$baseUrl    = 'https://uat.rwdpay.com/api/v1';
$secret     = getenv('RWDPAY_SECRET');
$merchantId = getenv('RWDPAY_MERCHANT_ID');

// Supply either transaction_id OR merchant_ref (or both, as long as they match)
$transactionId = 'RWD_1234567890'; // optional
$merchantRef   = '';              // optional

$payload   = $merchantId . ($transactionId ?: '') . ($merchantRef ?: '');
$signature = sign($payload, $secret);

// JSON request (you may also send as form post)
$requestBody = [
    'merchant_id'     => $merchantId,
    'transaction_id'  => $transactionId ?: null,
    'merchant_ref'    => $merchantRef ?: null,
    'signature'       => $signature,
];

// Remove nulls (clean payload)
$requestBody = array_filter($requestBody, fn($v) => $v !== null);

$ch = curl_init($baseUrl . '/inquiry');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'Accept: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode($requestBody, JSON_UNESCAPED_SLASHES),
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($response === false) {
    throw new RuntimeException('cURL error: ' . curl_error($ch));
}
curl_close($ch);

if ($httpCode === 404) {
    // No record found (or mismatch between transaction_id and merchant_ref)
    throw new RuntimeException('No record found (404). Check transaction_id/merchant_ref pairing.');
}

if ($httpCode < 200 || $httpCode >= 300) {
    throw new RuntimeException("RWDPay returned HTTP $httpCode: $response");
}

$data = json_decode($response, true);
if (!is_array($data)) {
    throw new RuntimeException('Invalid JSON response: ' . $response);
}

// Example fields: transaction_id, merchant_ref, amount, currency, status
// Use $data['status'] to decide next actions (reconcile, retry callback handling, etc.)
```

---

## 7. Security notes

- **Keep secrets server-side only.** Never embed your secret key in frontend JavaScript, mobile apps, or public repositories.
- **Always verify signatures** for redirect and callback payloads before updating payment state.
- **Use `hash_equals`** to mitigate timing attacks during signature comparison.
- **Idempotency:** callbacks may be retried. Update payment state idempotently using `transaction_id` and/or `merchant_ref`.
- **HTTPS required:** use TLS for `redirect_url` and `callback_url`.
- **Log safely:** never log secrets; consider masking sensitive transaction values if needed.
