SNAP Signature
Signature Generation and Validation
The below explains about signature generation & validation.
Asymmetric Signature
Asymmetric signature is used verify your access token request. The signature needs to be provided using a private key. Paydia's system supports the use of PKCS#8 for the private key, thus merchant should generate the signature with pkcs8_rsa_private_key.pem. The generated signature will be verified using a public key with rsa_public_key.pem. The algorithm used by Paydia is SHA256withRSA and the format type is Base64.
Asymmetric Key Generation
The following steps of asymmetric key generation.
Create Private Key
openssl genrsa -out rsa_private_key.pem 2048
Export Public Key
openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout
Private Key to PKCS#8 Encode
openssl pkcs8 -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem -nocrypt
After the merchant obtains the public key, they need to share it with Paydia to continue the signature process.
Signature Generation
The following steps are used to explain how to generate the digital signature used by the sender of APIs.
The following steps are used for Asymmetric Signature:
Prepare the data to do signature, the data are:
No Data Remarks Example 1 X-CLIENT-KEY Unique identifier for partner was generated by Paydia, or known as clientId 4abbcb6ce30229994c76169006e0dc9c 2 X-TIMESTAMP Transaction date time, in format YYYY-MM-DDTHH:mm:ss+07:00. Time must be in GMT+7 (Jakarta time) 2024-07-25T07:01:08+07:00 Compose the string to sign
<X-CLIENT-KEY> + "|" + <X-TIMESTAMP>
The following is the example:
4abbcb6ce30229994c76169006e0dc9c|2024-07-25T07:01:08+07:00
The signature string is generated from string to sign above with applying SHA-256 with RSA-2048 encryption using PKCS#8 or PKCS#1 private key, and then encode the result to base64. Refer to the tools section number 3 for the example process.
caqCS4JSfv1A2R8oTXT0tzQrZkAgkezPrs7yG17yKp1V0UmmXjW/Ju4eAHgVBqNTloRFwjKx63HNATEzL8QId4TJa3OudeRwGDZq2LWoEg+5YGVkjReFkQJiWYOBxaCsMZj8wSoE7ip/NlJc+5ufNM4m9A2UMjhwesnkPB9dd9wSuZJ8exMJMr7RgPq+9//5yzBZALXhdwpRvseccnlWizCWt0WGE83+pBV4wDv3hXYgChICjzMlmw8+nHoGtnPGlKKajWC9u8Ex8Mw9iTCWgycRNQVT+CaeLBZmkylKtJbHArENkWsnQsxrDKXEwSWgrLSR1OdxwUT4HN1t52+AHg==
Put the signature string into HTTP header "X-SIGNATURE". The following is the example value:
X-SIGNATURE: caqCS4JSfv1A2R8oTXT0tzQrZkAgkezPrs7yG17yKp1V0UmmXjW/Ju4eAHgVBqNTloRFwjKx63HNATEzL8QId4TJa3OudeRwGDZq2LWoEg+5YGVkjReFkQJiWYOBxaCsMZj8wSoE7ip/NlJc+5ufNM4m9A2UMjhwesnkPB9dd9wSuZJ8exMJMr7RgPq+9//5yzBZALXhdwpRvseccnlWizCWt0WGE83+pBV4wDv3hXYgChICjzMlmw8+nHoGtnPGlKKajWC9u8Ex8Mw9iTCWgycRNQVT+CaeLBZmkylKtJbHArENkWsnQsxrDKXEwSWgrLSR1OdxwUT4HN1t52+AHg==
Symmetric Signature
Symmetric signature is used to verify your SNAP service request. The signature needs to be provided using a client secret. The algorithm used by Paydia is SHA-512 HMAC and the format type is Base64.
Signature Generation
The following steps are used for Symmetric Signature:
Prepare the data to do signature, the data are:
No Data Remarks Example 1 HTTP METHOD Method on each API, for instance GET, POST, PUT, PATCH, and DELETE POST 2 RELATIVE PATH URL URL on each API /snap/v1.0/qr/qr-mpm-generate 3 ACCESS TOKEN B2B Access token b2b from Authorization Token Request eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJQYXlkaWEiLCJpYXQiOjE3MjE4OTU5OTksImV4cCI6MTcyMTk4MjM5OX0.eewVuMxRfBhWjEUqaxHn09a5Uw7KGqKuan5vRnV5xzw 4 HTTP BODY Minify request body and hash the request body with SHA-256, refer to step no 2 and 3 for the detail 0932935ef0fff8e78818c8f2d8da5bc85e1d3e4692500fec48ef9b084f70d127 5 X-TIMESTAMP Transaction date time, in format YYYY-MM-DDTHH:mm:ss+07:00. Time must be in GMT+7 (Jakarta time) 2024-07-25T15:33:58+07:00 Minify the request body
The following are the example value before and after minify the request body:
HTTP BODY BEFORE MINIFY:{
"merchantId": "240212001000000",
"storeId": "12345",
"terminalId": "Device 1",
"partnerReferenceNo": "9cd55c25-7257-416d-8d70-1c16303ed4ba",
"amount": {
"value": "10000.00",
"currency": "IDR"
},
"feeAmount": {
"value": "0.00",
"currency": "IDR"
},
"validityPeriod": "2024-12-31T23:59:59+07:00",
"additionalInfo": {
"callback": "https://webhook.site/36da5887-f4eb-46d5-a29c-91b765efb0aa"
}
}HTTP BODY AFTER MINIFY:{"merchantId":"240212001000000","storeId":"12345","terminalId":"Device 1","partnerReferenceNo":"9cd55c25-7257-416d-8d70-1c16303ed4ba","amount":{"value":"10000.00","currency":"IDR"},"feeAmount":{"value":"0.00","currency":"IDR"},"validityPeriod":"2024-12-31T23:59:59+07:00","additionalInfo":{"callback":"https:\/\/webhook.site\/36da5887-f4eb-46d5-a29c-91b765efb0aa"}}
Lowercase(HexEncode(SHA-256(RequestBody)))
The following is the example value:
0932935ef0fff8e78818c8f2d8da5bc85e1d3e4692500fec48ef9b084f70d127
Compose the string to sign
<HTTP METHOD> + ":" + <RELATIVE PATH URL> + ":" + <ACCESS TOKEN B2B> + ":" + LowerCase(HexEncode(SHA-256(Minify(<HTTP BODY>)))) + ":" + <X-TIMESTAMP>
The following is the example:
POST:/snap/v1.0/qr/qr-mpm-generate:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJQYXlkaWEiLCJpYXQiOjE3MjE4OTU5OTksImV4cCI6MTcyMTk4MjM5OX0.eewVuMxRfBhWjEUqaxHn09a5Uw7KGqKuan5vRnV5xzw:0932935ef0fff8e78818c8f2d8da5bc85e1d3e4692500fec48ef9b084f70d127:2024-07-25T15:33:58+07:00
The signature string is generated from string to sign above with applying SHA-512 HMAC encryption using Client Secret and then encode the result to base64. Refer to the tools section number 3 for the example process.
The following is the example value:
gA/Wjtpav1+8iod60CahNocr5Jpa20+4o/Gm91DN7KcMJKSygTfgxDYudOnElEU//Bhvm8y9GOw8svakl+vL3Q==
Put the signature string into HTTP header "X-SIGNATURE".
The following is the example value:
X-SIGNATURE: gA/Wjtpav1+8iod60CahNocr5Jpa20+4o/Gm91DN7KcMJKSygTfgxDYudOnElEU//Bhvm8y9GOw8svakl+vL3Q==
Signature Validation
The following steps are used to explain how to validate the digital signature used by the receiver of APIs:
Take the signature from HTTP header "X-SIGNATURE" from the sender of APIs.
The following is the example:
X-SIGNATURE: zy2qZG+iEvRakVi0rR2fj9O8N5luMYUKa07tdiPtVP6V6tYJgkCPrZ1NyJIZLnaDko21KIBwpXbOU+XuOuG\/GMUUP3v6R9Jx3ld4yyYs4QQzahvyyixxOsHphhTg\/ZHjfWBa5Pvg76i3WBfim1ZFsY4Y\/qgcVosbh1YGGrvu\/Wr8w2qbRv5\/05K9XQiln9r6cboiiIc7RjOCEaAHWf61s3ybPuxpiSM05zlvfZXYXDgtEnV7OIULdtAUK6sHNYE8HghwzZEJ0tM+Zy7PEuVh9gfsNqPmKJ0A\/JUT9IN1R9sGIwA6RnCcJVjmeqy7+q9A0s9LEcmhQOQZEX4Uwj\/uIA==
Compose the string to verify
<HTTP METHOD> + ":" + <RELATIVE PATH URL> + ":" + LowerCase(HexEncode(SHA-256(Minify(<HTTP BODY>)))) + ":" + <X-TIMESTAMP>
The following is the example:
POST:/v1.0/qr/qr-mpm-notify:d3a7ddb7196ce272c7e9868113b953db233e5a61f27383b5a561c32501cd9a94:2024-07-25T15:52:56+07:00
Verify the correctness of the signature based on SHA-256 with RSA-2048 encryption signing against the string to sign with provided public key of sender of APIs. In this case is Paydia public key.
If the verification is correct, then consume the request.
Code Example
The following examples are taken from various programming languages:
PHP
The following are the example asymmetric signature code of PHP:
<?php
// Define the RSA private key as a string
$privateKey = <<<EOD
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAlI9HYTRrHOHZN9FD8d8siddF4Eg8bftRxZdsbLKlGaTdwnVF
.....
-----END RSA PRIVATE KEY-----
EOD;
// Client credentials and timestamp for authentication
$clientId = "1c02148229fa32807d487c9706d7e97b";
$clientSecret = "c84e92221584e218cdbc0569673c7b48";
$timestamp = "2025-02-14T08:00:00+07:00";
// Predefined token for authentication
$token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJQYXlkaWEiLCJpYXQiOjE3Mzk0MjAyOTcsImV4cCI6MTczOTUwNjY5NywiZGF0YSI6eyJlbnYiOiJkZXZlbG9wbWVudCJ9fQ.GHniPRyVAzLNG0aM9FWSTCotguDPrzy9pv3MkM2zvHY";
// HTTP request method and API path
$method = "POST";
$path = "/snap/v1.0/qr/qr-mpm-generate";
// Payload data to be sent in the API request
$payload = [
"merchantId" => "250101001000000",
"partnerReferenceNo" => "d8e6404c-153a-48fc-98b2-0ecbc6579f76",
"amount" => [
"value" => "10000.00",
"currency" => "IDR"
],
"feeAmount" => [
"value" => "0.00",
"currency" => "IDR"
],
"validityPeriod" => "2025-02-15T10:00:00+07:00",
"additionalInfo" => [
"callback" => "https://callbackurlmerchant.com"
]
];
/**
* Generate authentication signature using RSA private key
*
* @param string $clientId Client ID
* @param string $timestamp Timestamp for the request
* @param string $privateKey PKCS#1 private key
*
* @return string Base64 encoded signature
*/
function generateSignatureAuth($clientId, $timestamp, $privateKey) {
$strToSign = $clientId . '|' . $timestamp;
openssl_sign($strToSign, $signature, openssl_pkey_get_private($privateKey), "sha256WithRSAEncryption");
return base64_encode($signature);
}
/**
* Generate service signature using HMAC-SHA512
*
* @param string $method HTTP request method (GET, POST, etc.)
* @param string $path API endpoint path
* @param string $token JWT token for authentication
* @param array $payload Request payload
* @param string $timestamp Timestamp for the request
* @param string $clientSecret Client secret key
*
* @return string Base64 encoded HMAC-SHA512 signature
*/
function generateSignatureService($method, $path, $token, $payload, $timestamp, $clientSecret) {
$payload = strtolower(bin2hex(hash('sha256', json_encode($payload, JSON_UNESCAPED_SLASHES), true)));
$strToSign = $method . ':' . $path . ':' . $token . ':' . $payload . ':' . $timestamp;
$hash = hash_hmac('sha512', $strToSign, $clientSecret, true);
return base64_encode($hash);
}
// Generate authentication signature
$signatureAuth = generateSignatureAuth($clientId, $timestamp, $privateKey);
print_r("Signature auth: \n" . $signatureAuth . "\n");
// Generate service signature
$signatureService = generateSignatureService($method, $path, $token, $payload, $timestamp, $clientSecret);
print_r("Signature service: \n" . $signatureService . "\n");
?>
Javascript
The following are the example asymmetric signature code of Javascript:
const crypto = require('crypto');
// RSA Private Key
const privateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAlI9HYTRrHOHZN9FD8d8siddF4Eg8bftRxZdsbLKlGaTdwnVF
.....
-----END RSA PRIVATE KEY-----`;
// Client credentials and timestamp for authentication
const clientId = "1c02148229fa32807d487c9706d7e97b";
const clientSecret = "c84e92221584e218cdbc0569673c7b48";
const timestamp = "2025-02-14T08:00:00+07:00";
// Predefined token for authentication
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJQYXlkaWEiLCJpYXQiOjE3Mzk0MjAyOTcsImV4cCI6MTczOTUwNjY5NywiZGF0YSI6eyJlbnYiOiJkZXZlbG9wbWVudCJ9fQ.GHniPRyVAzLNG0aM9FWSTCotguDPrzy9pv3MkM2zvHY";
// HTTP request method and API path
const method = "POST";
const path = "/snap/v1.0/qr/qr-mpm-generate";
const payload = {
merchantId: "250101001000000",
partnerReferenceNo: "d8e6404c-153a-48fc-98b2-0ecbc6579f76",
amount: {
value: "10000.00",
currency: "IDR"
},
feeAmount: {
value: "0.00",
currency: "IDR"
},
validityPeriod: "2025-02-15T10:00:00+07:00",
additionalInfo: {
callback: "https://callbackurlmerchant.com"
}
}
/**
* Generate authentication signature using RSA private key
*
* @param {string} clientId - Client ID
* @param {string} timestamp - Timestamp for the request
* @param {string} privateKey - RSA private key (PKCS#1 format)
* @returns {string} Base64 encoded signature
*/
function generateSignatureAuth(clientId, timestamp, privateKey) {
const sign = crypto.createSign('SHA256');
sign.update(`${clientId}|${timestamp}`);
sign.end();
return sign.sign(privateKey, 'base64');
}
/**
* Generate service signature using HMAC-SHA512
*
* @param {string} method - HTTP request method (GET, POST, etc.)
* @param {string} path - API endpoint path
* @param {string} token - JWT token for authentication
* @param {object} payload - Request payload
* @param {string} timestamp - Timestamp for the request
* @param {string} clientSecret - Client secret key
* @returns {string} Base64 encoded HMAC-SHA512 signature
*/
function generateSignatureService(method, path, token, payload, timestamp, clientSecret) {
const payloadHash = crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex');
const strToSign = `${method}:${path}:${token}:${payloadHash}:${timestamp}`;
return crypto.createHmac('sha512', clientSecret).update(strToSign).digest('base64');
}
// Generate authentication signature
const signatureAuth = generateSignatureAuth(clientId, timestamp, privateKey);
console.log("Signature Auth:\n", signatureAuth);
// Generate service signature
const signatureService = generateSignatureService(method, path, token, payload, timestamp, clientSecret);
console.log("Signature Service:\n", signatureService);
Go
The following are the example asymmetric signature code of Go:
package main
import (
"crypto"
"crypto/hmac"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"log"
)
/**
* Amount represents the monetary value and currency structure
*
* @field {string} Value - The monetary value as a string
* @field {string} Currency - The currency code (e.g., "IDR")
*/
type Amount struct {
Value string `json:"value"`
Currency string `json:"currency"`
}
/**
* AdditionalInfo contains extra information for the request
*
* @field {string} Callback - The callback URL for merchant notifications
*/
type AdditionalInfo struct {
Callback string `json:"callback"`
}
/**
* Payload represents the complete request payload structure
*
* @field {string} MerchantID - The unique identifier for the merchant
* @field {string} PartnerReferenceNo - The unique reference number for the transaction
* @field {Amount} Amount - The transaction amount details
* @field {Amount} FeeAmount - The fee amount details
* @field {string} ValidityPeriod - The expiration time of the request
* @field {AdditionalInfo} AdditionalInfo - Additional request parameters
*/
type Payload struct {
MerchantID string `json:"merchantId"`
PartnerReferenceNo string `json:"partnerReferenceNo"`
Amount Amount `json:"amount"`
FeeAmount Amount `json:"feeAmount"`
ValidityPeriod string `json:"validityPeriod"`
AdditionalInfo AdditionalInfo `json:"additionalInfo"`
}
// Constants for configuration
const (
privateKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAlI9HYTRrHOHZN9FD8d8siddF4Eg8bftRxZdsbLKlGaTdwnVF
.....
-----END RSA PRIVATE KEY-----`
clientID = "1c02148229fa32807d487c9706d7e97b"
clientSecret = "c84e92221584e218cdbc0569673c7b48"
timestamp = "2025-02-14T08:00:00+07:00"
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJQYXlkaWEiLCJpYXQiOjE3Mzk0MjAyOTcsImV4cCI6MTczOTUwNjY5NywiZGF0YSI6eyJlbnYiOiJkZXZlbG9wbWVudCJ9fQ.GHniPRyVAzLNG0aM9FWSTCotguDPrzy9pv3MkM2zvHY"
method = "POST"
path = "/snap/v1.0/qr/qr-mpm-generate"
)
/**
* Generate authentication signature using RSA private key
*
* @param {string} clientID - Client ID for authentication
* @param {string} timestamp - Timestamp for the request
* @param {string} privateKeyPEM - RSA private key in PEM format
* @returns {string, error} Base64 encoded signature and error if any
*/
func generateSignatureAuth(clientID, timestamp, privateKeyPEM string) (string, error) {
// Decode PEM block
block, _ := pem.Decode([]byte(privateKeyPEM))
if block == nil {
return "", fmt.Errorf("failed to parse PEM block containing private key")
}
// Parse RSA private key
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return "", fmt.Errorf("failed to parse private key: %v", err)
}
// Create signature
message := []byte(fmt.Sprintf("%s|%s", clientID, timestamp))
hashed := sha256.Sum256(message)
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed[:])
if err != nil {
return "", fmt.Errorf("failed to sign: %v", err)
}
return base64.StdEncoding.EncodeToString(signature), nil
}
/**
* Generate service signature using HMAC-SHA512
*
* @param {string} method - HTTP request method (GET, POST, etc.)
* @param {string} path - API endpoint path
* @param {string} token - JWT token for authentication
* @param {interface{}} payload - Request payload object
* @param {string} timestamp - Timestamp for the request
* @param {string} clientSecret - Client secret key
* @returns {string, error} Base64 encoded HMAC-SHA512 signature and error if any
*/
func generateSignatureService(method, path, token string, payload interface{}, timestamp, clientSecret string) (string, error) {
// Convert payload to JSON and calculate SHA256
payloadBytes, err := json.Marshal(payload)
if err != nil {
return "", fmt.Errorf("failed to marshal payload: %v", err)
}
payloadHash := sha256.Sum256(payloadBytes)
payloadHashHex := fmt.Sprintf("%x", payloadHash)
// Create string to sign
strToSign := fmt.Sprintf("%s:%s:%s:%s:%s", method, path, token, payloadHashHex, timestamp)
// Create HMAC-SHA512
h := hmac.New(sha512.New, []byte(clientSecret))
h.Write([]byte(strToSign))
return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
}
/**
* Main function to demonstrate the signature generation
* Creates a sample payload and generates both authentication and service signatures
*/
func main() {
// Create sample payload
payload := Payload{
MerchantID: "250101001000000",
PartnerReferenceNo: "d8e6404c-153a-48fc-98b2-0ecbc6579f76",
Amount: Amount{
Value: "10000.00",
Currency: "IDR",
},
FeeAmount: Amount{
Value: "0.00",
Currency: "IDR",
},
ValidityPeriod: "2025-02-15T10:00:00+07:00",
AdditionalInfo: AdditionalInfo{
Callback: "https://callbackurlmerchant.com",
},
}
// Generate authentication signature
signatureAuth, err := generateSignatureAuth(clientID, timestamp, privateKeyPEM)
if err != nil {
log.Fatalf("Failed to generate auth signature: %v", err)
}
fmt.Printf("Signature Auth:\n%s\n\n", signatureAuth)
// Generate service signature
signatureService, err := generateSignatureService(method, path, token, payload, timestamp, clientSecret)
if err != nil {
log.Fatalf("Failed to generate service signature: %v", err)
}
fmt.Printf("Signature Service:\n%s\n", signatureService)
}