Skip to main content

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.

  1. Create Private Key

    openssl genrsa -out rsa_private_key.pem 2048
  2. Export Public Key

    openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout
  3. 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:

  1. Prepare the data to do signature, the data are:

    NoDataRemarksExample
    1X-CLIENT-KEYUnique identifier for partner was generated by Paydia, or known as clientId4abbcb6ce30229994c76169006e0dc9c
    2X-TIMESTAMPTransaction 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
  2. Compose the string to sign

    <X-CLIENT-KEY> + "|" + <X-TIMESTAMP>

    The following is the example:

    4abbcb6ce30229994c76169006e0dc9c|2024-07-25T07:01:08+07:00

  3. 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==
  4. 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:

  1. Prepare the data to do signature, the data are:

    NoDataRemarksExample
    1HTTP METHODMethod on each API, for instance GET, POST, PUT, PATCH, and DELETEPOST
    2RELATIVE PATH URLURL on each API/snap/v1.0/qr/qr-mpm-generate
    3ACCESS TOKEN B2BAccess token b2b from Authorization Token RequesteyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJQYXlkaWEiLCJpYXQiOjE3MjE4OTU5OTksImV4cCI6MTcyMTk4MjM5OX0.eewVuMxRfBhWjEUqaxHn09a5Uw7KGqKuan5vRnV5xzw
    4HTTP BODYMinify request body and hash the request body with SHA-256, refer to step no 2 and 3 for the detail0932935ef0fff8e78818c8f2d8da5bc85e1d3e4692500fec48ef9b084f70d127
    5X-TIMESTAMPTransaction 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
  2. 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"}}
  3. Lowercase(HexEncode(SHA-256(RequestBody)))

    The following is the example value:

    0932935ef0fff8e78818c8f2d8da5bc85e1d3e4692500fec48ef9b084f70d127
  4. 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
  5. 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==
  6. 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:

  1. 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==
  2. 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
  3. 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.

  4. 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
<?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:

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:

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)
}