Skip to main content

Signature Algorithm

Sign every API request with RSA SHA256 to authenticate securely. All requests must be signed before they are sent.

How It Works

1Sort JSON
2Base64 Encode
3Build String
4Sign & Send

Required Headers

Add these to every signed request:

AuthorizationBearer <access_token>
X-Signaturesha256 <signature>
X-Nonce-Strrandom string
X-Timestampunix timestamp
Content-Typeapplication/json

Step by Step

Step 1Sort JSON Alphabetically

Sort all keys alphabetically, including nested objects, then remove all spaces and newlines so the JSON is compact.

Method: POST

important

Both the data object and every nested object must be sorted. Before encoding, replace special characters:

CharacterReplace With
<\u003c
>\u003e
&\u0026
note

The request body depends on which API endpoint you are calling. The body below is just an EXAMPLE.

Example Request (before sorting)
JSON
1{
2"order": {
3 "title": "hello",
4 "detail": "",
5 "additionalData": "world",
6 "amount": 10,
7 "currencyType": "MYR",
8 "id": "7211"
9},
10"customer": {
11 "userId": "13245876",
12 "email": ""
13},
14"method": [],
15"type": "WEB_PAYMENT",
16"storeId": "1608123035564538121",
17"redirectUrl": "https://revenuemonster.my",
18"notifyUrl": "https://dev-rm-api.ap.ngrok.io",
19"layoutVersion": "v3"
20}
Sorted + Compact
Text
{"additionalData":"world","amount":10,"currencyType":"MYR",...}

Request Body Parameters

orderOBJECTrequired

Object of order

titleSTRINGrequired

Order title. Max 32 characters.

Example: "Sales"

detailSTRINGrequired

Order detail. Max 600 characters.

Example: "1 x iPhone X; 2 x SAMSUNG S8"

additionalDataSTRINGrequired

Additional order description.

Example: "Sales"

amountINTEGERrequired

Amount of order in cents. Minimum is RM 0.10 (amount: 10). Required only when isPrefillAmount = true.

Example: 100

currencyTypeSTRINGrequired

Currency notation. Currently only MYR is supported.

Example: "MYR"

idSTRING

Your internal order reference ID.

Example: "6170506694335521334"

customerOBJECTrequired

Object of customer

userIdSTRINGrequired

Required if tokenization is enabled.

Example: "13245876"

emailSTRING

Customer email address.

Example: ""

countryCodeSTRING

Customer country code.

Example: ""

phoneNumberSTRING

Customer phone number.

Example: ""

methodARRAYrequired

RM currently supported method

Example: []

typeOBJECTrequired

Object of type

typeSTRINGrequired

Use WEB_PAYMENT for browser-based payments or MOBILE_PAYMENT for mobile app payments.

Example: "WEB_PAYMENT"

storeIdSTRINGrequired

ID of the store to create QR code

Example: "10946114768247530"

redirectUrlSTRINGrequired

URL to redirect after payment is made

Example: "https://google.com"

notifyUrlSTRINGrequired

Webhook URL that RM will call with the payment result

Example: "https://google.com"

layoutVersionSTRING

Select layout for Web payment. v1 / v2 (Credit Card) / v3 (Credit Card and FPX)

Example: "v3"

Step 2Base64 Encode

Encode the compact, sorted JSON string using Base64.

Base64 Encoded
Text
ewogICAgIm9yZGVyIjogewogICAgCSJ0aXRsZSI6ICJoZWxsbyIsCiAgICAJImRldGFpbCI6IC
IiLAogICAgCSJhZGRpdGlvbmFsRGF0YSI6ICJ3b3JsZCIsCgkgICAgImFtb3VudCI6IDEwLAoJIC
AgICJjdXJyZW5jeVR5cGUiOiAiTVlSIiwKCSAgICAiaWQiOiAgIjcyMTEiCiAgICB9LAogICAgIm
N1c3RvbWVyIjogewogICAgInVzZXJJZCI6ICIiLAogICAgImVtYWlsIjogIiIKfSwKICAgICJtZX
Rob2QiOltdLAogICAgInR5cGUiOiAiV0VCX1BBWU1FTlQiLAogICAgInN0b3JlSWQiOiAiMTYwOD
EyMzAzNTU2NDUzODEyMSIsCiAgICAicmVkaXJlY3RVcmwiOiAiaHR0cHM6Ly9yZXZlbnVlbW9uc3
Rlci5teSIsCiAgICAibm90aWZ5VXJsIjogImh0dHBzOi8vZGV2LXJtLWFwaS5hcC5uZ3Jvay5pby
IsCiAgICAibGF5b3V0VmVyc2lvbiI6InYzIgp9

Step 3Construct the Signing String

Combine the encoded body with the request parameters, joined by & in alphabetical order.

important
  • If the body is empty, the data parameter can be skipped.
  • If you are verifying a callback signature, the requestUrl can be skipped.
dataSTRINGrequired

Base64-encoded data body from Step 2.

Example: eyJhZGRpdGlvbmFs...

methodSTRINGrequired

HTTP call method, lowercase.

Example: "post"

nonceStrSTRINGrequired

A unique random string. Must not be reused within 120 seconds.

Example: "VYNknZohxwicZMaWbNdBKUrnrxDtaRhN"

requestUrlSTRINGrequired

The exact API URL being called, including the full path.

Example: "https://sb-open.revenuemonster.my/v3/payment/online"

signTypeSTRINGrequired

The signing algorithm. Always this value.

Example: "sha256"

timestampSTRINGrequired

UNIX timestamp (UTC). Must be within 120 seconds of server time.

Example: "1527407052"

Signing String
Text
data=eyJhZGRpdGlvbmFs...&method=post&nonceStr=VYNknZohxwicZMaWbNdBKUrnrxDtaRhN&requestUrl=https://sb-open.revenuemonster.my/v3/payment/online&signType=sha256&timestamp=1527407052

Step 4Sign with Your Private Key

Sign the signing string using RSA SHA256 with your private key. Base64-encode the result.

important

Make sure the matching public key has been uploaded to the RM Merchant Portal, or verification will fail.

Signature
Text
sha256 IrBg6t73VsH7ieEnQDB4CXHFjMWUkp8Dtddpxqw+4Gvz6Tag7Dx6nrfAt2ofYK8xZN9aBCvAKAfmAOGWIXnsTXfhFBnMA2kadiga7ufUJ81ozyhllbiliRM2ugw1OcqSTLRHWBPhrVwhHBxgDiG9wbuI3FKURrz+CufYYakFoCw=

Code Examples

const crypto = require('crypto');
const fs = require('fs');

// Load your RSA private key from a secure location — never hardcode it.
const privateKey = fs.readFileSync(process.env.RM_PRIVATE_KEY_PATH, 'utf8');
const url = 'https://sb-open.revenuemonster.my/v3/payment/online';

const body = {
order: {
title: 'hello',
detail: '',
additionalData: 'world',
amount: 10,
currencyType: 'MYR',
id: '7211',
},
customer: {
userId: '13245876',
email: '',
},
method: [],
type: 'WEB_PAYMENT',
storeId: '1608123035564538121',
redirectUrl: 'https://revenuemonster.my',
notifyUrl: 'https://dev-rm-api.ap.ngrok.io',
layoutVersion: 'v3',
};

// 1. Sort keys recursively, then compact and escape special characters.
function sortObject(value) {
if (Array.isArray(value)) {
return value.map(sortObject);
}
if (value && typeof value === 'object') {
return Object.keys(value)
.sort()
.reduce((acc, key) => {
acc[key] = sortObject(value[key]);
return acc;
}, {});
}
return value;
}

const compact = JSON.stringify(sortObject(body))
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/&/g, '\\u0026');

// 2. Base64-encode the compact JSON.
const data = Buffer.from(compact).toString('base64');

// 3. Build the signing string (parameters in alphabetical order).
const nonceStr = crypto.randomBytes(16).toString('hex');
const timestamp = Math.floor(Date.now() / 1000).toString();
const signString =
`data=${data}` +
`&method=post` +
`&nonceStr=${nonceStr}` +
`&requestUrl=${url}` +
`&signType=sha256` +
`&timestamp=${timestamp}`;

// 4. Sign with RSA-SHA256, then Base64-encode the signature.
const signature = crypto
.createSign('RSA-SHA256')
.update(signString)
.sign(privateKey, 'base64');

// Send with headers:
// X-Signature: `sha256 ${signature}`
// X-Nonce-Str: nonceStr
// X-Timestamp: timestamp

Response

itemOBJECT

Response payload.

checkoutIdSTRING

Code to identify the web payment session.

Example: "1548316308361173347"

urlSTRING

Checkout URL. To change the base URL, replace the domain to your desired URL.

Example: "https://sb-pg.revenuemonster.my/checkout?checkoutId=1548316308361173347"

codeSTRING

SUCCESS if the call succeeded. Otherwise returns an error code object. See Appendix: Error Codes.

Example: "SUCCESS"

Example Response
JSON
1{
2"item": {
3 "checkoutId": "1548316308361173347",
4 "url": "https://sb-pg.revenuemonster.my/checkout?checkoutId=1548316308361173347"
5},
6"code": "SUCCESS"
7}

Common Mistakes

❌ JSON keys not sorted

Sort alphabetically including nested objects. Check every level.

❌ Extra spaces in JSON

Make it compact: {"a":1} not {"a": 1}. Remove all whitespace.

❌ Forgot to escape special characters

Replace <\u003c, >\u003e, &\u0026 before encoding.

❌ Timestamp too old

Must be within 120 seconds of server UTC time. Sync your clock.

❌ Reused nonce

Generate a new nonce for each request. Don't reuse within 120 seconds.

❌ Wrong private key

Verify your private key matches the public key uploaded to the portal.


Security Best Practices

✅ DO
  • Store private keys securely
  • Use environment variables
  • Generate a unique nonce per request
  • Verify certificate validity
  • Rotate keys periodically
❌ DON'T
  • Hardcode private keys
  • Commit keys to git
  • Reuse nonce values
  • Skip the timestamp check
  • Share private keys

Prerequisites — App & RSA Keys

Signing requires an application and an RSA key pair from the Merchant Portal. If you haven't set those up yet:

info

See Set Up Your Application — create an app, obtain your clientId / clientSecret, generate your RSA keys, and (optionally) use the Signature Debugger.


Troubleshooting — INVALID_REQUEST_SIGNATURE

If you receive an INVALID_REQUEST_SIGNATURE error, the response includes a debug object showing exactly what the server computed at each step.

note

Compare the preVerifyContent steps below with your own output to find where they diverge.

Debug Response
JSON
1{
2"debug": {
3 "preVerifyContent": {
4 "step1": {
5 "content": "{\"layoutVersion\":\"v2\",\"method\":[\"GOBIZ_MY\"],\"notifyUrl\":\"https://dev-rm-api.ap.ngrok.io\",\"order\":{\"additionalData\":\"world\",\"amount\":10,\"currencyType\":\"MYR\",\"detail\":\"hello\",\"id\":\"721115\",\"title\":\"hello\"},\"redirectUrl\":\"https://revenuemonster.my\",\"storeId\":\"10946114768247530\",\"type\":\"WEB_PAYMENT\"}",
6 "remark": "Sort the json key alphabetically"
7 },
8 "step2": {
9 "content": "eyJsYXlvdXRWZXJzaW9uIjoidjIiLCJtZXRob2QiOlsiR09CSVpfTVkiXSwibm90aWZ5VXJsIjoiaHR0cHM6Ly9kZXYtcm0tYXBpLmFwLm5ncm9rLmlvIiwib3JkZXIiOnsiYWRkaXRpb25hbERhdGEiOiJ3b3JsZCIsImFtb3VudCI6MTAsImN1cnJlbmN5VHlwZSI6Ik1ZUiIsImRldGFpbCI6ImhlbGxvIiwiaWQiOiI3MjExMTUiLCJ0aXRsZSI6ImhlbGxvIn0sInJlZGlyZWN0VXJsIjoiaHR0cHM6Ly9yZXZlbnVlbW9uc3Rlci5teSIsInN0b3JlSWQiOiIxMDk0NjExNDc2ODI0NzUzMCIsInR5cGUiOiJXRUJfUEFZTUVOVCJ9",
10 "remark": "Encode the data using Base64 format"
11 },
12 "step3": {
13 "content": "data=eyJsYXlvd...&method=post&nonceStr=XAYZRZNLGCKSTURRFKBIGYALUKLCLJOG&requestUrl=https://sb-open.revenuemonster.my/v3/payment/online&signType=sha256&timestamp=1599467903",
14 "remark": "Construct plain text parameters on this format. If the body is empty, the data parameter can be omitted."
15 },
16 "step4": {
17 "content": "data=eyJsYXlvd...&method=post&nonceStr=XAYZRZNLGCKSTURRFKBIGYALUKLCLJOG&requestUrl=https://sb-open.revenuemonster.my/v3/payment/online&signType=sha256&timestamp=1599467903",
18 "remark": "Sign this content using sha256 with rsa private key and make sure the public key has been uploaded to the portal."
19 },
20 "step5": {
21 "remark": "Pass the generated signature in the X-Signature header, prefixed with the sign type. Example: sha256 {{ signatureContent }}"
22 }
23 },
24 "requestHeader": {
25 "X-Nonce-Str": {
26 "currentValue": "XAYZRZNLGCKSTURRFKBIGYALUKLCLJOG",
27 "isValid": true,
28 "remark": "The nonce string must not contain spaces and must be unique for at least 120 seconds."
29 },
30 "X-Signature": {
31 "currentValue": "sha256 XvedDW8H2gqGL5gMzTHqDy1PXX3OqRF09WuQDkeCDwuinOAsPstcPOSefUwkyHPM9WPNKKHyR5qXbKNLC7UgQyGi8Ynio03kDo0p+g3BqXaUT1tpo5D8kv42Kh2S8CW4RkX2Dkf+Yxi2XMQ8l3kzPZaRyhudaGerUZony4Npzf63p4+oTBbXE01uX/4x/WL57+zkaaVRc1KlJsLdGsBmLlPOHLana7udJffJyxXhOmyokBuJ4GoOC8JpDG9oaKCNMZ88ow9CWWB0yRPrK2KeaEDwzCm2Jh8IFKw1gS6avQAwsjychZWv5XmAXkZ8ZQrnLXJquA09QpLxPTtOeQC9SA==",
32 "isValid": false,
33 "remark": "The signature is invalid."
34 },
35 "X-Timestamp": {
36 "currentValue": "1599467903",
37 "isValid": false,
38 "remark": "The timestamp must be in UTC and within 120 seconds of the server time."
39 }
40 }
41},
42"error": {
43 "code": "INVALID_REQUEST_SIGNATURE",
44 "message": "The request signature is invalid"
45}
46}

Common Causes

CauseFix
Private key doesn't match the public key uploaded to the Merchant PortalRe-upload the correct public key
JSON keys not sorted alphabeticallyCheck nested objects too
JSON body still contains spaces or newlines before Base64 encodingMake the JSON compact
Special characters (<, >, &) not replaced before encodingReplace with \u003c, \u003e, \u0026
X-Timestamp is more than 120 seconds away from server UTC timeSync your system clock
X-Nonce-Str was reused within a 120-second windowGenerate a new nonce for each request
Amount is in cents — "amount": 100 = RM 1.00Multiply the amount by 100

Need Help?