• favicon
    RWDPay Payment Gateway
RWDPay Payment Gateway API — Integration Guide
  • 1. Endpoints
  • 2. Integration flows
    • 2.1. Standard hosted payment (browser form POST)
    • 2.2. Payment link (server-to-server, then redirect)
    • 2.3. Status inquiry (server-to-server)
  • 3. Data definitions
    • 3.1. Standards
    • 3.2. Payment status codes
    • 3.3. Signature
  • 4. API requests
    • 4.1. Create payment — standard hosted flow
    • 4.2. Create payment — payment link flow
    • 4.3. Status inquiry
  • 5. Redirect and callback payload
  • 6. Implementation examples (PHP)
    • 6.1. Generate and verify signatures
    • 6.2. Standard hosted payment: render an auto-posting form
    • 6.3. Payment link: create link via API (cURL)
    • 6.4. Handle callback/redirect and verify signature
    • 6.5. Status inquiry via API (cURL)
  • 7. Security notes
  1. Integration Guide

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

DescriptionEnvironmentBase URL
Merchant PortalUAThttps://portal-uat.rwdpay.com/login
Merchant PortalProductionhttps://portal.rwdpay.com/login
APIUAThttps://uat.rwdpay.com/api/v1
APIProductionhttps://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.
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.
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).

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

TypeDescriptionExample
CurrencyISO 4217 currency code (3 characters)MYR
AmountMinor units of the currency (e.g., cents for MYR)1500 (i.e., MYR 15.00)

3.2. Payment status codes

CodeMeaning
000Payment successful
100Payment initiated
101Payment processing
200Timeout (payment not completed before expiry)
201User cancelled
202Payment 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
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

FieldTypeRequiredDescription
merchant_idstring✓Merchant ID provided by RWDPay
merchant_refstring✓Unique reference from merchant (case-insensitive). Allowed: alphanumeric + ., -, _. Max 255 chars.
amountinteger✓Amount in minor units. See Standards.
currencystring✓Currency code. See Standards.
signaturestring✓HMAC-SHA256 signature. Payload: merchant_id + merchant_ref + amount + currency
redirect_urlstringOptionalURL to redirect the shopper to after payment completion.
callback_urlstringOptionalServer-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.

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

FieldTypeRequiredDescription
merchant_idstring✓Merchant ID provided by RWDPay
transaction_idstringRequired when merchant_ref is missingUnique transaction ID generated by RWDPay
merchant_refstringRequired when transaction_id is missingYour merchant reference
signaturestring✓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.
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:

FieldTypeDescription
merchant_idstringMerchant ID provided by RWDPay
transaction_idstringUnique transaction ID generated by RWDPay
merchant_refstringYour merchant reference
amountintegerAmount in minor units. See Standards.
currencystringCurrency code. See Standards.
statusstringStatus code. See Payment status codes.
signaturestringHMAC-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>

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

<?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

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