This document explains how to integrate with the RWDPay Payment Gateway, including environments, integration flows, request/response fields, signature rules, and PHP implementation examples.
| 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.
Use this when you want to post an HTML form to RWDPay from the shopper’s browser.
High-level steps
redirect_url and (optionally) sends a server-to-server callback to callback_url.Use this when you want your server to create a payment and receive a payment URL, then redirect the shopper.
High-level steps
payment_url.payment_url.Use this when you need to query the latest transaction status from your server (e.g., reconciliation, callback not received, or manual checks).
| 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) |
| Code | Meaning |
|---|---|
000 |
Payment successful |
100 |
Payment initiated |
101 |
Payment processing |
200 |
Timeout (payment not completed before expiry) |
201 |
User cancelled |
202 |
Payment failed |
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);
}
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;
};
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
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
type Response = {
transaction_id: string;
merchant_ref: string;
amount: number;
currency: string;
status: string;
};
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 |
<?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);
}
<?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;
<?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';
<?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.)
hash_equals to mitigate timing attacks during signature comparison.transaction_id and/or merchant_ref.redirect_url and callback_url.