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
- Your backend prepares and signs the payment fields.
- Your frontend posts an HTML form to RWDPay.
- RWDPay redirects the shopper to
redirect_urland (optionally) sends a server-to-server callback tocallback_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
- Your backend calls the API to create a payment and receives
payment_url. - Your frontend redirects the shopper to
payment_url. - 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
| 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
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. |
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
| 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_idandmerchant_refare 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:
| 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>
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_equalsto mitigate timing attacks during signature comparison. - Idempotency: callbacks may be retried. Update payment state idempotently using
transaction_idand/ormerchant_ref. - HTTPS required: use TLS for
redirect_urlandcallback_url. - Log safely: never log secrets; consider masking sensitive transaction values if needed.