Request Signature
Request Signature
Applies to API v2.0 (RSA). When you call Pay-In / Pay-Out / Inquiry, build X-SIGNATURE with your merchant private key.
Most common mistake
Do not reuse this formula (or your merchant private key) to verify callbacks. See Callback Signature (tradeNo|X-TIMESTAMP + Platform Public Key).
Required headers
| Header | Required | Notes |
|---|---|---|
Content-Type | Yes | application/json |
X-TIMESTAMP | Yes | Pattern yyyy-MM-dd'T'HH:mm:ssXXX, e.g. 2024-12-30T18:30:36+07:00 |
X-SIGNATURE | Yes | Base64(SHA256withRSA(...)) |
X-PARTNER-ID | Yes | Your Merchant ID |
For Pay-In / Pay-Out: X-PARTNER-ID must equal merchant.merchantId in the JSON body.
Formula
Gateway verifies (v2):
stringToSign = X-TIMESTAMP + "|" + merchant_secret + "|" + minify(requestBody)
X-SIGNATURE = Base64( SHA256withRSA(stringToSign, merchantPrivateKey) )
| Part | Source |
|---|---|
X-TIMESTAMP | Same value as header X-TIMESTAMP |
merchant_secret | Merchant Portal → Configuration Info |
minify(requestBody) | Exact JSON body you send (whitespace outside strings removed) |
| Private key | Your merchant RSA private key (PKCS#8, Base64) |
Minify body
Remove whitespace outside JSON strings before signing. The signed string must be identical to the HTTP body.
Formatted JSON (example)
{
"area": 10,
"callbackUrl": "https://docs.smilepayz.com/en/",
"merchant": {
"merchantId": "20019"
},
"money": {
"amount": 10000,
"currency": "IDR"
},
"orderNo": "20019c5b63c4b-e34a-4855-9b20-d4b",
"paymentMethod": "QRIS",
"purpose": "Purpose For Transaction from Java SDK"
}
Minified (what you sign and send):
{"area":10,"callbackUrl":"https://docs.smilepayz.com/en/","merchant":{"merchantId":"20019"},"money":{"amount":10000,"currency":"IDR"},"orderNo":"20019c5b63c4b-e34a-4855-9b20-d4b","paymentMethod":"QRIS","purpose":"Purpose For Transaction from Java SDK"}
Do not change the body after signing.
Example string to sign
Replace {merchant_secret} with your own secret from Configuration Info:
2024-12-30T18:30:36+07:00|{merchant_secret}|{"area":10,"callbackUrl":"https://docs.smilepayz.com/en/","merchant":{"merchantId":"20019"},"money":{"amount":10000,"currency":"IDR"},"orderNo":"20019c5b63c4b-e34a-4855-9b20-d4b","paymentMethod":"QRIS","purpose":"Purpose For Transaction from Java SDK"}
Request headers example
Content-Type: application/json
X-TIMESTAMP: 2024-12-30T18:30:36+07:00
X-SIGNATURE: {Base64 signature}
X-PARTNER-ID: {merchantId}
X-TIMESTAMP must parse as above and stay within 24 hours of Smilepayz server time (Asia/Jakarta). Sync clocks with NTP.
Code samples
Open your language. Keys from the portal are raw Base64 (private: PKCS#8; public: X.509/SPKI) — no PEM headers. Pass an already-minified body.
Java
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
/** Build X-SIGNATURE for Smilepayz API v2. */
public class RequestSigner {
public static String sign(
String timestamp,
String merchantSecret,
String minifiedBody,
String privateKeyBase64) throws Exception {
// timestamp|merchant_secret|minify(body)
String stringToSign = timestamp + "|" + merchantSecret + "|" + minifiedBody;
byte[] keyBytes = Base64.getDecoder().decode(privateKeyBase64);
PrivateKey privateKey = KeyFactory.getInstance("RSA")
.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(stringToSign.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(signature.sign());
}
}
PHP
<?php
/** Build X-SIGNATURE for Smilepayz API v2. $privateKeyBase64 = raw Base64 from portal (no PEM). */
function sign(string $timestamp, string $merchantSecret, string $minifiedBody, string $privateKeyBase64): string {
// timestamp|merchant_secret|minify(body)
$stringToSign = $timestamp . '|' . $merchantSecret . '|' . $minifiedBody;
$keyPem = "-----BEGIN PRIVATE KEY-----
"
. chunk_split($privateKeyBase64, 64, "
")
. "-----END PRIVATE KEY-----
";
$privateKey = openssl_pkey_get_private($keyPem);
if ($privateKey === false) {
throw new RuntimeException('Invalid private key');
}
openssl_sign($stringToSign, $sig, $privateKey, OPENSSL_ALGO_SHA256);
return base64_encode($sig);
}
Python
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
def sign(timestamp: str, merchant_secret: str, minified_body: str, private_key_base64: str) -> str:
"""Build X-SIGNATURE for Smilepayz API v2. private_key_base64 = raw Base64 from portal."""
# timestamp|merchant_secret|minify(body)
string_to_sign = f"{timestamp}|{merchant_secret}|{minified_body}"
private_key = serialization.load_der_private_key(
base64.b64decode(private_key_base64),
password=None,
)
sig = private_key.sign(
string_to_sign.encode("utf-8"),
padding.PKCS1v15(),
hashes.SHA256(),
)
return base64.b64encode(sig).decode("utf-8")
Node.js
const crypto = require("crypto");
/** Build X-SIGNATURE for Smilepayz API v2. privateKeyBase64 = raw Base64 from portal. */
function sign(timestamp, merchantSecret, minifiedBody, privateKeyBase64) {
// timestamp|merchant_secret|minify(body)
const stringToSign = `${timestamp}|${merchantSecret}|${minifiedBody}`;
const privateKey = crypto.createPrivateKey({
key: Buffer.from(privateKeyBase64, "base64"),
format: "der",
type: "pkcs8",
});
const signer = crypto.createSign("RSA-SHA256");
signer.update(stringToSign, "utf8");
return signer.sign(privateKey, "base64");
}
Golang
package signature
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"fmt"
)
// Sign builds X-SIGNATURE for Smilepayz API v2. privateKeyBase64 = raw Base64 from portal.
func Sign(timestamp, merchantSecret, minifiedBody, privateKeyBase64 string) (string, error) {
// timestamp|merchant_secret|minify(body)
stringToSign := timestamp + "|" + merchantSecret + "|" + minifiedBody
keyBytes, err := base64.StdEncoding.DecodeString(privateKeyBase64)
if err != nil {
return "", err
}
key, err := x509.ParsePKCS8PrivateKey(keyBytes)
if err != nil {
return "", err
}
rsaKey, ok := key.(*rsa.PrivateKey)
if !ok {
return "", fmt.Errorf("not an RSA private key")
}
hash := sha256.Sum256([]byte(stringToSign))
sig, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, crypto.SHA256, hash[:])
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(sig), nil
}
Signature Test (Merchant Portal)
- Sign in to the Merchant Portal.
- Open Configuration (Sandbox: left menu; Production: Settings → Configuration).
- Open Signature Test.
- Enter
X-PARTNER-ID,Merchant Secret,X-TIMESTAMP, minified body JSON, and your private key → Generate Signature.
Where to get credentials
Merchant Secret / X-PARTNER-ID / Platform Public Key: Configuration Info. Merchant private key: API Setting → Generate. See Integration Info.

Quick troubleshooting
| Problem | Check |
|---|---|
| Request signature mismatch | Body minified and unchanged; order timestamp|secret|body; PKCS#8 Base64 private key |
X-PARTNER-ID rejected | Must equal merchant.merchantId in body (Pay-In / Pay-Out) |
| Timestamp rejected | Format yyyy-MM-dd'T'HH:mm:ssXXX; within 24h of Asia/Jakarta; use NTP |

