RWDPay Logo

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.


Table of contents


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.
User (Merchant Front-end)Merchant ServerRWDPayCheckout initiatedReturn signed form fieldsPOST /payment (HTML form)Redirect to redirect_url (status payload)POST callback_url (status payload)User (Merchant Front-end)Merchant ServerRWDPay

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.
User (Merchant Front-end)Merchant ServerRWDPayCheckout initiatedPOST /payment (JSON)payment_url returnedRedirect instruction (payment_url)Open payment_url and complete paymentRedirect to redirect_url (status payload)POST callback_url (status payload)User (Merchant Front-end)Merchant ServerRWDPay

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).

Merchant ServerRWDPayPOST /inquiry (transaction_id or merchant_ref)Status response (JSON)Merchant ServerRWDPay

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.

PHP signing helper

<?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.
currency string Currency code. See 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.

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.

JSON response

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

Signature payload rule

JSON response

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.
currency string Currency code. See Standards.
status string Status code. See 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
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
$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>
<?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
$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
$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