Signature Callback Validation
Validasi signature digunakan untuk mengautentikasi request yang dikirimkan ke url callback merchant.
Public Key
Dalam validasi signature callback merchant memerlukan public key milik winpay pada prosesnya. Untuk production akan dikirimkan via email dengan format tertentu.
Rumus Signature
Signature pada validasi callback mengambil data pada header request yang dikirimkan winpay.
stringToSign = HTTPMethod +":"+ EndpointUrl +":"+ Lowercase(HexEncode(SHA-256(minify(RequestBody)))) + ":" + TimeStamp
signature = base64_decode(SHA256withRSA(private_key, stringToSign))
Source Code Validasi Signature
Virtual Account
- PHP
- Node JS
- Go
$path = '/sandbox_prod/url_listener.php/v1.0/transfer-va/payment';
$timestamp = '2024-01-11T08:57:55+07:00'; //ambil dari header X-Timestamp
$signature = 'Zng8tJgtK2lPd8CP89KyO1OGEKXn1tFfXevTGIn5IhHYDpobp7+4uvuczP5HwldghO5mzkh03v6wnggoZev8M2RyKegbrRaIr66KbAgr6sfKfH9MfkXFcEKpF/am8QMr4oExKPdYTdGEr6pq6m1CzUjFQsyu9z6JuMGrjXrxFXU='; //ambil dari header X-Signature
$httpMethod = 'POST';
$partnerId = '170041'; //ambil dari header X-Partner-Id
$body = [
"partnerServiceId"=> " 9042",
"customerNo"=> "00000009",
"virtualAccountNo"=> " 904200000009",
"virtualAccountName"=> "WINPAY - fiandi",
"trxId"=> "INV-000000023220",
"paymentRequestId"=> "45539",
"paidAmount"=> [
"value"=> "10000.00",
"currency"=> "IDR"
],
"trxDateTime"=> "2024-01-11T08:57:55+07:00",
"additionalInfo"=> [
"contractId"=> "si1cd5671d-2ffe-4cca-aff0-b8ee9bc1c041",
"channel"=> "BSI"
]
];
$payload = json_encode($body, JSON_UNESCAPED_SLASHES);
$stringToSignArr = [
$httpMethod,
$path,
strtolower(bin2hex(hash('sha256', $payload, true))),
$timestamp
];
$stringToSign = implode(':', $stringToSignArr);
try {
$publicKey = openssl_get_publickey($publicKey);
$verify = openssl_verify($stringToSign, base64_decode($signature), $publicKey, OPENSSL_ALGO_SHA256);
if($verify !== 1){
$response = [
'message' => 'Cannot verify signature'
];
print_r($response);
} else {
$response = [
'responseCode' => '2002500',
'responseMessage' => 'Successful'
];
print_r($response);
}
} catch(Exception $e) {
$response = [
'message' => 'Invalid signature {'.$e->getMessage().'}'
];
print_r($response);
}
const path = "/sandbox_prod/url_listener.php/v1.0/transfer-va/payment";
const timestamp = "2024-01-11T08:57:55+07:00"; //ambil dari header X-Timestamp
const signature =
"Zng8tJgtK2lPd8CP89KyO1OGEKXn1tFfXevTGIn5IhHYDpobp7+4uvuczP5HwldghO5mzkh03v6wnggoZev8M2RyKegbrRaIr66KbAgr6sfKfH9MfkXFcEKpF/am8QMr4oExKPdYTdGEr6pq6m1CzUjFQsyu9z6JuMGrjXrxFXU="; //ambil dari header X-Signature
const httpMethod = "POST";
const partnerId = "170041"; //ambil dari header X-Partner-Id
const body = {
partnerServiceId: " 9042",
customerNo: "00000009",
virtualAccountNo: " 904200000009",
virtualAccountName: "WINPAY - fiandi",
trxId: "INV-000000023220",
paymentRequestId: "45539",
paidAmount: {
value: "10000.00",
currency: "IDR",
},
trxDateTime: "2024-01-11T08:57:55+07:00",
additionalInfo: {
contractId: "si1cd5671d-2ffe-4cca-aff0-b8ee9bc1c041",
channel: "BSI",
},
};
const payload = JSON.stringify(body);
const stringToSignArr = [
httpMethod,
path,
crypto.createHash("sha256").update(payload).digest("hex"),
timestamp,
];
const stringToSign = stringToSignArr.join(":");
try {
const publicKey = fs.readFileSync("publicKey.pem");
const verify = crypto
.createVerify("sha256")
.update(stringToSign)
.verify(publicKey, Buffer.from(signature, "base64"));
if (!verify) {
const response = {
message: "Cannot verify signature",
};
console.log(response);
} else {
const response = {
responseCode: "2002500",
responseMessage: "Successful",
};
console.log(response);
}
} catch (error) {
const response = {
message: "Invalid signature {" + error.message + "}",
};
console.log(response);
}
package main
import (
"bytes"
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"strings"
)
func main() {
path := "/sandbox_prod/url_listener.php/v1.0/transfer-va/payment"
tStamp := "2024-01-11T08:57:55+07:00" // ambil dari header X-Timestamp
method := "POST" // http method
signature := "Zng8tJgtK2lPd8CP89KyO1OGEKXn1tFfXevTGIn5IhHYDpobp7+4uvuczP5HwldghO5mzkh03v6wnggoZev8M2RyKegbrRaIr66KbAgr6sfKfH9MfkXFcEKpF/am8QMr4oExKPdYTdGEr6pq6m1CzUjFQsyu9z6JuMGrjXrxFXU=" // ambil dari header X-Signature
payloadStr := `{
"partnerServiceId" : " 9042",
"customerNo" : "00000009",
"virtualAccountNo" : " 904200000009",
"virtualAccountName" : "WINPAY - fiandi",
"trxId" : "INV-000000023220",
"paymentRequestId" : "45539",
"paidAmount" : {
"value" : "10000.00",
"currency" : "IDR"
},
"trxDateTime" : "2024-01-11T08:57:55+07:00",
"additionalInfo" : {
"contractId" : "si1cd5671d-2ffe-4cca-aff0-b8ee9bc1c041",
"channel" : "BSI"
}
}`
minifyBody, _ := Minify([]byte(payloadStr))
stringToSign := fmt.Sprintf("%s:%s:%s:%s", method, path, string(minifyBody), tStamp)
signatureOk := ValidateSignature(stringToSign, signature)
fmt.Println("Apakah signature valid?")
fmt.Println(signatureOk)
}
func Minify(body []byte) ([]byte, error) {
buff := new(bytes.Buffer)
errCompact := json.Compact(buff, body)
if errCompact != nil {
newErr := fmt.Errorf("failure encountered compacting json := %v", errCompact)
return nil, newErr
}
b, err := io.ReadAll(buff)
if err != nil {
readErr := fmt.Errorf("read buffer error encountered := %v", err)
return nil, readErr
}
// LowerCase(HexEncode(SHA-256(stringToMinify)))
hasher := sha256.New()
hasher.Write([]byte(b))
hash := hex.EncodeToString(hasher.Sum(nil))
return []byte(strings.ToLower(hash)), nil
}
func ValidateSignature(data string, signature string) bool {
// Ubah dengan public key dari WINPAY
publicKey := "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHGK10J1oBbCD75Wy8P5rL8zzCz9lpHZ7OIHVaO4vlX0tjpKD887ldaTLl4Vdhc6I88OHscV/ijQ7OQ07IpWLAoSm7VKpuiUDt9xUx9dCHAIrH6DDNOI95z2b6jxwh81ZTC+LCDsEb5b797dmxa7Kv8kABjoNt8JR33E9p3d7uzrAgMBAAE="
pubKeyBytes, err := base64.StdEncoding.DecodeString(publicKey)
if err != nil {
panic(err)
}
parsedPubKey, err := x509.ParsePKIXPublicKey(pubKeyBytes)
if err != nil {
panic(err)
}
pubKey, ok := parsedPubKey.(*rsa.PublicKey)
if !ok {
panic("public key is not RSA")
}
hasher := sha256.New()
hasher.Write([]byte(data))
signatureBytes, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
panic(err)
}
err = rsa.VerifyPKCS1v15(pubKey, crypto.SHA256, hasher.Sum(nil), signatureBytes)
if err != nil {
return false
}
return true
}
E-Wallet
- PHP
- Node JS
- Go
$path = '/v1/test';
$timestamp = '2024-01-11T17:01:35+07:00'; //ambil dari header X-Timestamp
$signature = 'SbplyMG0x4igUO8ZqkgUDqNXIzZ6eryz1eNWFfUT499/ulgCLKDJifPtdQH/WBWg5BnKTn+AaKK6inll7h7gJbojC/85TH1QNFnzz8fR4ds+BmFhQaYhys1G5upm4h9x/2hFIznFsEYlc+Ggljs5kXi9wezsltlEkAf5w6snCf4='; //ambil dari header X-Signature
$httpMethod = 'POST';
$partnerId = '170041'; //ambil dari header X-Partner-Id
$body = [
"originalPartnerReferenceNo"=> "0000000000591y",
"originalReferenceNo"=> "45588",
"merchantId"=> 170041,
"amount"=> [
"value"=> "1000",
"currency"=> "IDR"
],
"latestTransactionStatus"=> "00",
"additionalInfo"=> [
"channel"=> "OVO",
"contractId"=> "ovb7b5599b-cce7-4f0f-8c34-fa283185945b"
]
];
$payload = json_encode($body, JSON_UNESCAPED_SLASHES);
$stringToSignArr = [
$httpMethod,
$path,
strtolower(bin2hex(hash('sha256', $payload, true))),
$timestamp
];
$stringToSign = implode(':', $stringToSignArr);
try {
$publicKey = openssl_get_publickey($publicKey);
$verify = openssl_verify($stringToSign, base64_decode($signature), $publicKey, OPENSSL_ALGO_SHA256);
if($verify !== 1){
$response = [
'message' => 'Cannot verify signature'
];
print_r($response);
} else {
$response = [
'responseCode' => '2005600',
'responseMessage' => 'Successful'
];
print_r($response);
}
} catch(Exception $e) {
$response = [
'message' => 'Invalid signature {'.$e->getMessage().'}'
];
print_r($response);
}
const path = "/v1/test";
const timestamp = "2024-01-11T17:01:35+07:00"; //ambil dari header X-Timestamp
const signature =
"SbplyMG0x4igUO8ZqkgUDqNXIzZ6eryz1eNWFfUT499/ulgCLKDJifPtdQH/WBWg5BnKTn+AaKK6inll7h7gJbojC/85TH1QNFnzz8fR4ds+BmFhQaYhys1G5upm4h9x/2hFIznFsEYlc+Ggljs5kXi9wezsltlEkAf5w6snCf4="; //ambil dari header X-Signature
const httpMethod = "POST";
const partnerId = "170041"; //ambil dari header X-Partner-Id
const body = {
originalPartnerReferenceNo: "0000000000591y",
originalReferenceNo: "45588",
merchantId: 170041,
amount: {
value: "1000",
currency: "IDR",
},
latestTransactionStatus: "00",
additionalInfo: {
channel: "OVO",
contractId: "ovb7b5599b-cce7-4f0f-8c34-fa283185945b",
},
};
const payload = JSON.stringify(body);
const stringToSignArr = [
httpMethod,
path,
hash(payload).toLowerCase(),
timestamp,
];
const stringToSign = stringToSignArr.join(":");
try {
const publicKey = openssl_get_publickey(publicKey);
const verify = openssl_verify(
stringToSign,
Buffer.from(signature, "base64"),
publicKey,
"sha256"
);
if (verify !== 1) {
const response = {
message: "Cannot verify signature",
};
console.log(response);
} else {
const response = {
responseCode: "2005600",
responseMessage: "Successful",
};
console.log(response);
}
} catch (e) {
const response = {
message: "Invalid signature {" + e.getMessage() + "}",
};
console.log(response);
}
package main
import (
"bytes"
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"strings"
)
func main() {
path := "/v1/test"
tStamp := "2024-01-11T17:01:35+07:00" // ambil dari header X-Timestamp
method := "POST" // http method
signature := "SbplyMG0x4igUO8ZqkgUDqNXIzZ6eryz1eNWFfUT499/ulgCLKDJifPtdQH/WBWg5BnKTn+AaKK6inll7h7gJbojC/85TH1QNFnzz8fR4ds+BmFhQaYhys1G5upm4h9x/2hFIznFsEYlc+Ggljs5kXi9wezsltlEkAf5w6snCf4=" // ambil dari header X-Signature
payloadStr := `{
"originalPartnerReferenceNo": "0000000000591y",
"originalReferenceNo": "45588",
"merchantId": 170041,
"amount": {
"value": "1000",
"currency": "IDR"
},
"latestTransactionStatus": "00",
"additionalInfo": {
"channel": "OVO",
"contractId": "ovb7b5599b-cce7-4f0f-8c34-fa283185945b"
}
}`
minifyBody, _ := Minify([]byte(payloadStr))
stringToSign := fmt.Sprintf("%s:%s:%s:%s", method, path, string(minifyBody), tStamp)
signatureOk := ValidateSignature(stringToSign, signature)
fmt.Println("Apakah signature valid?")
fmt.Println(signatureOk)
}
func Minify(body []byte) ([]byte, error) {
buff := new(bytes.Buffer)
errCompact := json.Compact(buff, body)
if errCompact != nil {
newErr := fmt.Errorf("failure encountered compacting json := %v", errCompact)
return nil, newErr
}
b, err := io.ReadAll(buff)
if err != nil {
readErr := fmt.Errorf("read buffer error encountered := %v", err)
return nil, readErr
}
// LowerCase(HexEncode(SHA-256(stringToMinify)))
hasher := sha256.New()
hasher.Write([]byte(b))
hash := hex.EncodeToString(hasher.Sum(nil))
return []byte(strings.ToLower(hash)), nil
}
func ValidateSignature(data string, signature string) bool {
// Ubah dengan public key dari WINPAY
publicKey := "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHGK10J1oBbCD75Wy8P5rL8zzCz9lpHZ7OIHVaO4vlX0tjpKD887ldaTLl4Vdhc6I88OHscV/ijQ7OQ07IpWLAoSm7VKpuiUDt9xUx9dCHAIrH6DDNOI95z2b6jxwh81ZTC+LCDsEb5b797dmxa7Kv8kABjoNt8JR33E9p3d7uzrAgMBAAE="
pubKeyBytes, err := base64.StdEncoding.DecodeString(publicKey)
if err != nil {
panic(err)
}
parsedPubKey, err := x509.ParsePKIXPublicKey(pubKeyBytes)
if err != nil {
panic(err)
}
pubKey, ok := parsedPubKey.(*rsa.PublicKey)
if !ok {
panic("public key is not RSA")
}
hasher := sha256.New()
hasher.Write([]byte(data))
signatureBytes, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
panic(err)
}
err = rsa.VerifyPKCS1v15(pubKey, crypto.SHA256, hasher.Sum(nil), signatureBytes)
if err != nil {
return false
}
return true
}
QRIS
- PHP
- Node JS
- Go
$path = '/sandbox_prod/url_listener.php/v1.0/qr/qr-mpm-notify';
$timestamp = '2024-01-11T16:36:57+07:00'; //ambil dari header X-Timestamp
$signature = 'Zeqi6FIJ1Po8JU2tc0yqfqEbiHNfBYkvesbKIcPIEomsBpqriWrD5cqMH2Tb8Ys8U5CSqBhSliM8KnQ0YIyk444mCEe4M4eKnGVYA3YVfE3Bg1IOviugqzCeNPKxU8kxARdaXVlCo7s+tA9roHt+fE5ZTRYeGkAEC3ChM8277aI='; //ambil dari header X-Signature
$httpMethod = 'POST';
$partnerId = '170041'; //ambil dari header X-Partner-Id
$body = [
"originalReferenceNo"=> "45584",
"originalPartnerReferenceNo"=> "abctoxfzxc",
"latestTransactionStatus"=> "00",
"amount"=> [
"value"=> "45000",
"currency"=> "IDR"
],
"additionalInfo"=> [
"channel"=> "QRIS",
"contractId"=> "qr748ad360-9755-40af-a201-e44b45c4568f",
"brandName"=> "DANA",
"rrn"=> "027364710019",
"buyerRef"=> "WINPAY",
"terminalId"=> null
]
];
$payload = json_encode($body, JSON_UNESCAPED_SLASHES);
$stringToSignArr = [
$httpMethod,
$path,
strtolower(bin2hex(hash('sha256', $payload, true))),
$timestamp
];
$stringToSign = implode(':', $stringToSignArr);
try {
$publicKey = openssl_get_publickey($publicKey);
$verify = openssl_verify($stringToSign, base64_decode($signature), $publicKey, OPENSSL_ALGO_SHA256);
if($verify !== 1){
$response = [
'message' => 'Cannot verify signature'
];
print_r($response);
} else {
$response = [
'responseCode' => '2005200',
'responseMessage' => 'Successful'
];
print_r($response);
}
} catch(Exception $e) {
$response = [
'message' => 'Invalid signature {'.$e->getMessage().'}'
];
print_r($response);
}
const path = "/sandbox_prod/url_listener.php/v1.0/qr/qr-mpm-notify";
const timestamp = "2024-01-11T16:36:57+07:00"; //ambil dari header X-Timestamp
const signature =
"Zeqi6FIJ1Po8JU2tc0yqfqEbiHNfBYkvesbKIcPIEomsBpqriWrD5cqMH2Tb8Ys8U5CSqBhSliM8KnQ0YIyk444mCEe4M4eKnGVYA3YVfE3Bg1IOviugqzCeNPKxU8kxARdaXVlCo7s+tA9roHt+fE5ZTRYeGkAEC3ChM8277aI="; //ambil dari header X-Signature
const httpMethod = "POST";
const partnerId = "170041"; //ambil dari header X-Partner-Id
const body = {
originalReferenceNo: "45584",
originalPartnerReferenceNo: "abctoxfzxc",
latestTransactionStatus: "00",
amount: {
value: "45000",
currency: "IDR",
},
additionalInfo: {
channel: "QRIS",
contractId: "qr748ad360-9755-40af-a201-e44b45c4568f",
brandName: "DANA",
rrn: "027364710019",
buyerRef: "WINPAY",
terminalId: null,
},
};
const payload = JSON.stringify(body);
const stringToSignArr = [
httpMethod,
path,
crypto.createHash("sha256").update(payload).digest("hex").toLowerCase(),
timestamp,
];
const stringToSign = stringToSignArr.join(":");
try {
const publicKey = fs.readFileSync("publicKey.pem");
const verify = crypto
.createVerify("sha256")
.update(stringToSign)
.verify(publicKey, Buffer.from(signature, "base64"));
if (!verify) {
const response = {
message: "Cannot verify signature",
};
console.log(response);
} else {
const response = {
responseCode: "2005200",
responseMessage: "Successful",
};
console.log(response);
}
} catch (error) {
const response = {
message: "Invalid signature {" + error.message + "}",
};
console.log(response);
}
package main
import (
"bytes"
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"strings"
)
func main() {
path := "/sandbox_prod/url_listener.php/v1.0/qr/qr-mpm-notify"
tStamp := "2024-01-11T16:36:57+07:00" // ambil dari header X-Timestamp
method := "POST" // http method
signature := "Zeqi6FIJ1Po8JU2tc0yqfqEbiHNfBYkvesbKIcPIEomsBpqriWrD5cqMH2Tb8Ys8U5CSqBhSliM8KnQ0YIyk444mCEe4M4eKnGVYA3YVfE3Bg1IOviugqzCeNPKxU8kxARdaXVlCo7s+tA9roHt+fE5ZTRYeGkAEC3ChM8277aI=" // ambil dari header X-Signature
payloadStr := `{
"originalReferenceNo": "45584",
"originalPartnerReferenceNo": "abctoxfzxc",
"latestTransactionStatus": "00",
"amount": {
"value": "45000",
"currency": "IDR"
},
"additionalInfo": {
"channel": "QRIS",
"contractId": "qr748ad360-9755-40af-a201-e44b45c4568f",
"brandName": "DANA",
"rrn": "027364710019",
"buyerRef": "WINPAY",
"terminalId": null
}
}`
minifyBody, _ := Minify([]byte(payloadStr))
stringToSign := fmt.Sprintf("%s:%s:%s:%s", method, path, string(minifyBody), tStamp)
signatureOk := ValidateSignature(stringToSign, signature)
fmt.Println("Apakah signature valid?")
fmt.Println(signatureOk)
}
func Minify(body []byte) ([]byte, error) {
buff := new(bytes.Buffer)
errCompact := json.Compact(buff, body)
if errCompact != nil {
newErr := fmt.Errorf("failure encountered compacting json := %v", errCompact)
return nil, newErr
}
b, err := io.ReadAll(buff)
if err != nil {
readErr := fmt.Errorf("read buffer error encountered := %v", err)
return nil, readErr
}
// LowerCase(HexEncode(SHA-256(stringToMinify)))
hasher := sha256.New()
hasher.Write([]byte(b))
hash := hex.EncodeToString(hasher.Sum(nil))
return []byte(strings.ToLower(hash)), nil
}
func ValidateSignature(data string, signature string) bool {
// Ubah dengan public key dari WINPAY
publicKey := "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHGK10J1oBbCD75Wy8P5rL8zzCz9lpHZ7OIHVaO4vlX0tjpKD887ldaTLl4Vdhc6I88OHscV/ijQ7OQ07IpWLAoSm7VKpuiUDt9xUx9dCHAIrH6DDNOI95z2b6jxwh81ZTC+LCDsEb5b797dmxa7Kv8kABjoNt8JR33E9p3d7uzrAgMBAAE="
pubKeyBytes, err := base64.StdEncoding.DecodeString(publicKey)
if err != nil {
panic(err)
}
parsedPubKey, err := x509.ParsePKIXPublicKey(pubKeyBytes)
if err != nil {
panic(err)
}
pubKey, ok := parsedPubKey.(*rsa.PublicKey)
if !ok {
panic("public key is not RSA")
}
hasher := sha256.New()
hasher.Write([]byte(data))
signatureBytes, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
panic(err)
}
err = rsa.VerifyPKCS1v15(pubKey, crypto.SHA256, hasher.Sum(nil), signatureBytes)
if err != nil {
return false
}
return true
}