Signature Callback Validation
Signature validation is used to authenticate requests sent to the merchant callback url.
Public Key
In validating the callback signature, the merchant requires winpay's public key in the process. Production will be sent via email with a certain format.
Signature Formula
The signature on the callback validation takes data in the request header sent by winpay.
stringToSign = HTTPMethod +":"+ EndpointUrl +":"+ Lowercase(HexEncode(SHA-256(minify(RequestBody)))) + ":" + TimeStamp
signature = base64_decode(SHA256withRSA(private_key, stringToSign))
Source Code Signature Validation
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
}