Langsung ke konten utama

Request Signature

Sekitar 3 menit

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

HeaderRequiredNotes
Content-TypeYesapplication/json
X-TIMESTAMPYesPattern yyyy-MM-dd'T'HH:mm:ssXXX, e.g. 2024-12-30T18:30:36+07:00
X-SIGNATUREYesBase64(SHA256withRSA(...))
X-PARTNER-IDYesYour 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) )
PartSource
X-TIMESTAMPSame value as header X-TIMESTAMP
merchant_secretMerchant Portal → Configuration Info
minify(requestBody)Exact JSON body you send (whitespace outside strings removed)
Private keyYour 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)

  1. Sign in to the Merchant Portalopen in new window.
  2. Open Configuration (Sandbox: left menu; Production: Settings → Configuration).
  3. Open Signature Test.
  4. 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.

Merchant portal — Signature Test

Quick troubleshooting

ProblemCheck
Request signature mismatchBody minified and unchanged; order timestamp|secret|body; PKCS#8 Base64 private key
X-PARTNER-ID rejectedMust equal merchant.merchantId in body (Pay-In / Pay-Out)
Timestamp rejectedFormat yyyy-MM-dd'T'HH:mm:ssXXX; within 24h of Asia/Jakarta; use NTP