A Webhook is an API that provides with real-time information.
The Merchant can receive the notifications either by email or endpoint (notification assembling to the status inquiry – Checkout Status), however only a notification will be sent for each transaction.
The type of webhook sent by the SIBS Gateway must be configured on the SIBS Gateway Backoffice platform. If the merchant chooses the Webhook URL type, the merchant must enter the URL where they wish to receive the webhook. If the Merchant chooses the Webhook E-mail type, the Merchant must enter the default e-mail address that will receive the notifications sent by SIBS Gateway. The webhook can be configured per payment method and per merchant, store or terminal.
To configure the Webhook URL type, the merchant needs to make the following technical settings:
- The webhook endpoint must be available in TLS 1.2 and on ports 80 or 443;
- The merchant must store and enter an encryption key on the SIBS Gateway V2 Backoffice platform or use the key suggested by the platform in order to decrypt the webhook received;
- The webhook is encrypted with AES-256;
- The Merchant must enter a specific e-mail address where they will receive notifications regarding problems with the webhook.
For the e-mail type Webhook, only one notification will be sent per transaction. If the Merchant chooses the Webhook URL type and does not acknowledge the reception of the notification, SIBS Gateway will trigger the Webhook Retry System.
In the webhook structure, the “notificationID” field plays a crucial part in the webhook sending process. This field identifies the webhook that was sent by SIBS Gateway and will be used by the Merchant to send the acknowledge of the reception of the webhook to SIBS Gateway, in addition to the “statusCode” field with value “200” and the “statusMsg” field with value “Success”. This will prevent the Webhook Retry System from triggering and, in case of URL type webhook, from sending an e-mail with the failed notifications.
In case there’s communication or system issues, the Webhook Retry System cannot guarantee the message sequence, especially if the time difference between the notifications is smaller than the time it takes to process them. Once the issues are resolved, new notifications will arrive in real-time and old notifications will be resent. If for some reason the webhooks are not retried or the Merchant is not able to open the webhook, SIBS Gateway provides the “Checkout Status” service where the Merchant can access information about the status of a transaction.
Decrypt
The content of notification is encrypted to protect data from fraud attempts. When converting human-readable string to hexadecimal format, we use UTF-8.Encryption algorithm : AESBlock mode : GCMPadding : NoneInitialization Vector : In HTTP header (X-Initialization-Vector)Authentication Tag : In HTTP header (X-Authentication-Tag)
Format of body: Base64 Format of Initialization Vector: Base64
Secret: O0Bur9uhZkS54NkwFhVyeutED6DhLbOQUBDt3i3W/C4=
X-Authentication-Tag: Ytw9bzOS1pXqizAKMGXVQ==
Content-Type: text/plain
X-Initialization-Vector: Ldo3OyWNgRchSF3C
Body before decryption
WgErmJOV6wg3BuRkrgZLUUnh57BYzhIzvBFdpadHRsc43UcjtZEevRGDIDu3YxocXMXe8O+xQpMRxwTJPv766IaNqUiUEjAIj
ZSMEYCZ0pBursUYB+9nB4eqNUiAS2MJ9sR+Cj2iBf6G6KXLfp9K6dK7c0UED5XrJwbovY8X8pMyxktFTEaflp0e76ZywsCQvt
qEtqNz9uYEyqmAANbsBwbwyWpkCC8H1kZN2fV3CYetW1CTPmWdPp3C18Yfh826NN4XlKu1VmUmea70PyjmRKSsjPXpfrRX8ud
elVIK2WTFtnRxD4x588d1nlGY5D5DQmJ8KYZzfvjTmDXGAPiRIEGuXp8h6rBQXS8P/m1llBtboGgQv4MmW3zvq0G6KFlYIcM=
Body after decryption
{
"returnStatus": {
"statusMsg": "Success",
"statusCode": "000"
},
"paymentStatus": "Success",
"paymentMethod": "CARD",
"transactionID": "WebhookTest",
"amount": {
"currency": "EUR",
"value": 10.0
},
"merchant": {
"terminalId": 1000000
},
"paymentType": "PURS",
"notificationID": "f153c248-e7be-4c12-8d88-6c9f1f3b83e4"
}
Examples
C#
using System;
using System.Security.Cryptography;
using System.Text;
public static class Program {
public static void Main() {
byte[] secret = System.Convert.FromBase64String(“6fNDiYU0T0/evFpmfycNai/AqF24i+rT0OmuVw0/sGQ=”);
byte[] ciphertext = System.Convert.FromBase64String(“9bIjURJIcwoKvQr+ifOTH3HbMX+IqmsRqHuG/I1GfbSX89JE5DcWh/p8QROC5pRAuYZ7″+“ln7RSkHXJdZpVz1LFQ2859WsetvHHui7qYmfxATOO1j0AQuPdAD3FeRH0kR4s/v3c2nV8″+“1DnUXFCnQER/+VWrYdbu5vn8gm+diSE6CHvkK+ODy0ebVi5O6VBnWVjgBUG33VwWiAyIl”+“7Ik435V55WnZgynH3GfbVYoGwZ5UhYtn3yw2yruiLAKu6VTBvnh/ZJP21cHCJSF6NPSd+8″+“1gzWFU/+ECm3cf3uBbCkmKmL7HxRhRxhG0lMtX6ELZOXuw3eDJ1BTu+sSMkV/5Xk+5XX48″+“XmP6CGZ7KmP7Q3Fw1kZmhn0unFyv0Gw8PjT1Ohny/HMgNl16I=”);
byte[] nonce = System.Convert.FromBase64String(“RYjpCMtUmK54T6Lk”);
byte[] tag = System.Convert.FromBase64String(“FUajWHmZjP4A5qaa1G0kxw==”);
using (var aes = new AesGcm(secret))
{
var plaintextBytes = new byte[ciphertext.Length];
aes.Decrypt(nonce, ciphertext, tag, plaintextBytes);
string decrypt = Encoding.UTF8.GetString(plaintextBytes);
Console.WriteLine(decrypt);
}
}
}
Java
import java.security.Security;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import com.google.common.base.Charsets;
import org.apache.commons.lang3.ArrayUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
// For Java and JVM-based languages, you might need to install unrestricted policy file for JVM,
// which is provided by Sun. Please refer BouncyCastle FAQ if you get
// java.lang.SecurityException: Unsupported keysize or algorithm parameters or
// java.security.InvalidKeyException: Illegal key size.
// If you cannot install unrestricted policy file for JVM because of some reason, you can try with reflection: See here.
public class Test {
public static void main(String[] args) {
try {
Security.addProvider(new BouncyCastleProvider());
// Data from configuration
String keyFromConfiguration = "6fNDiYU0T0/evFpmfycNai/AqF24i+rT0OmuVw0/sGQ=";
// Data from server
String ivFromHttpHeader = "RYjpCMtUmK54T6Lk";
String authTagFromHttpHeader = "FUajWHmZjP4A5qaa1G0kxw==";
String httpBody = "9bIjURJIcwoKvQr+ifOTH3HbMX+IqmsRqHuG/I1GfbSX89JE5DcWh/p8QROC5pRAuYZ7"+"ln7RSkHXJdZpVz1LFQ2859WsetvHHui7qYmfxATOO1j0AQuPdAD3FeRH0kR4s/v3c2nV8"+"1DnUXFCnQER/+VWrYdbu5vn8gm+diSE6CHvkK+ODy0ebVi5O6VBnWVjgBUG33VwWiAyIl"+"7Ik435V55WnZgynH3GfbVYoGwZ5UhYtn3yw2yruiLAKu6VTBvnh/ZJP21cHCJSF6NPSd+8"+"1gzWFU/+ECm3cf3uBbCkmKmL7HxRhRxhG0lMtX6ELZOXuw3eDJ1BTu+sSMkV/5Xk+5XX48"+"XmP6CGZ7KmP7Q3Fw1kZmhn0unFyv0Gw8PjT1Ohny/HMgNl16I=";
// Convert data to process
byte[] key = Base64.getDecoder().decode(keyFromConfiguration);
byte[] iv = Base64.getDecoder().decode(ivFromHttpHeader);
byte[] authTag = Base64.getDecoder().decode(authTagFromHttpHeader);
byte[] encryptedText = Base64.getDecoder().decode(httpBody);
// Unlike other programming language, We have to append auth tag at the end of
// encrypted text in Java
byte[] cipherText = ArrayUtils.addAll(encryptedText, authTag);
// Prepare decryption
SecretKeySpec keySpec = new SecretKeySpec(key, 0, 32, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
// Decrypt
byte[] bytes = cipher.doFinal(cipherText);
System.out.println(new String(bytes, Charsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
}
}
}
PHP
function sodium_decrypt( $webhookSecret, $iv_from_http_header, $http_body , $auth_tag_from_http_header ){
$key = mb_convert_encoding($webhookSecret, "UTF-8", "BASE64");
$iv = mb_convert_encoding($iv_from_http_header, "UTF-8", "BASE64");
$cipher_text = mb_convert_encoding($http_body, "UTF-8", "BASE64") . mb_convert_encoding($auth_tag_from_http_header, "UTF-8", "BASE64");
$result = sodium_crypto_aead_aes256gcm_decrypt($cipher_text, "", $iv, $key);
return $result;
}
$webhookSecret = "6fNDiYU0T0/evFpmfycNai/AqF24i+rT0OmuVw0/sGQ=";
$iv_from_http_header = "RYjpCMtUmK54T6Lk";
$auth_tag_from_http_header = "FUajWHmZjP4A5qaa1G0kxw==";
$http_body = "9bIjURJIcwoKvQr+ifOTH3HbMX+IqmsRqHuG/I1GfbSX89JE5DcWh/p8QROC5pRAuYZ7" .
"ln7RSkHXJdZpVz1LFQ2859WsetvHHui7qYmfxATOO1j0AQuPdAD3FeRH0kR4s/v3c2nV8" . "1DnUXFCnQER/+VWrYdbu5vn8gm+diSE6CHvkK+ODy0ebVi5O6VBnWVjgBUG33VwWiAyIl" . "7Ik435V55WnZgynH3GfbVYoGwZ5UhYtn3yw2yruiLAKu6VTBvnh/ZJP21cHCJSF6NPSd+8" .
"1gzWFU/+ECm3cf3uBbCkmKmL7HxRhRxhG0lMtX6ELZOXuw3eDJ1BTu+sSMkV/5Xk+5XX48" .
"XmP6CGZ7KmP7Q3Fw1kZmhn0unFyv0Gw8PjT1Ohny/HMgNl16I=";
// Decrypt message
$result = sodium_decrypt($webhookSecret, $iv_from_http_header, $http_body , $auth_tag_from_http_header);
print($result);
Python
import base64
from Cryptodome.Cipher import AES
def decrypt_AES_GCM(encryptedMsg, authTag, secretKey, iv):
iv = base64.b64decode(iv)
encryptedMsg = base64.b64decode(encryptedMsg)
secretKey = base64.b64decode(secretKey)
authTag = base64.b64decode(authTag)
aesCipher = AES.new(secretKey, AES.MODE_GCM, iv)
plaintext = aesCipher.decrypt_and_verify(encryptedMsg, authTag)
return plaintext
example = {
"encoded" : "9bIjURJIcwoKvQr+ifOTH3HbMX+IqmsRqHuG/I1GfbSX89JE5DcWh/p8QROC5pRAuYZ7" \
"ln7RSkHXJdZpVz1LFQ2859WsetvHHui7qYmfxATOO1j0AQuPdAD3FeRH0kR4s/v3c2nV8" \
"1DnUXFCnQER/+VWrYdbu5vn8gm+diSE6CHvkK+ODy0ebVi5O6VBnWVjgBUG33VwWiAyIl" \
"7Ik435V55WnZgynH3GfbVYoGwZ5UhYtn3yw2yruiLAKu6VTBvnh/ZJP21cHCJSF6NPSd+8" \
"1gzWFU/+ECm3cf3uBbCkmKmL7HxRhRxhG0lMtX6ELZOXuw3eDJ1BTu+sSMkV/5Xk+5XX48"
"XmP6CGZ7KmP7Q3Fw1kZmhn0unFyv0Gw8PjT1Ohny/HMgNl16I=",
"iv" : "RYjpCMtUmK54T6Lk",
"tag" : "FUajWHmZjP4A5qaa1G0kxw==",
"secret" : "6fNDiYU0T0/evFpmfycNai/AqF24i+rT0OmuVw0/sGQ="
}
result = decrypt_AES_GCM(example['encoded'], example['tag'], example['secret'], example['iv'])
print(result)