A webhook is an API that delivers real-time information regarding changes in the payment status of transactions.
Merchants can receive notifications either by email or through an endpoint (notification assembled for the status inquiry – Checkout Status). However, only one notification will be sent for each transaction.
Notifications are sent directly to the endpoint (URL) configured by the merchant. The parameterization of this endpoint needs to be done in the SIBS Backoffice.
Each time the SIBS Gateway receives an update on the payment status of a transaction, a notification will be sent with the new transaction paymentStatus.
Every day, the SIBS Gateway sends a summarized email with the latest failed notifications (the email needs to be registered in the SIBS Backoffice).
There is no guarantee on the order of messages, especially if the time difference between the notifications is smaller than the time it takes to process them or due to any communication or systems issues. Once the issues are resolved, new notifications will arrive in real time, and old notifications will be resent. If no notification is received, the “Checkout Status” option should be used before rejecting any transaction.
Notifications are sent as HTTPS callbacks (webhooks) to an endpoint on your server. Please ensure that you have a valid SSL certificate chain. Self-signed certificates are not valid.
How it works?
To receive notifications, you need a server that has the following:
- An endpoint capable of receiving HTTP POST requests.
- An open TCP port for HTTPS traffic (port 443 or 80) with TLSv1.2 encryption.
Depending on your network and security requirements, you may also need to whitelist our network on your firewall. To ensure that your server correctly handles notifications, we require you to acknowledge every type of notification with an HTTP 200 and a response that includes:
{
"statusCode": "200",
"statusMsg": "Success",
"notificationID": "2533e456-5e36-42c8-9eea-7961902f185e"
}
When your server receives a notification, it should:
- Decrypt the notification
- Store the notification in your database
- Acknowledge the notification with an HTTP 200 OK as explained before.
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 : AES
- Block mode : GCM
- Padding : None
- Initialization Vector : In HTTP header (X-Initialization-Vector)
- Authentication Tag : In HTTP header (X-Authentication-Tag)
- Format of body: Base64
- Format of Initialization Vector: Base64
Test notification example:
Secret: O0Bur9uhZkS54NkwFhVyeutED6DhLbOQUBDt3i3W/C4=
X-Authentication-Tag: Ytw9bzOS1pXqizAKMGXVQ==
Content-Type: text/plain
X-Initialization-Vector: Ldo3OyWNgRchSF3C
Body before decryption:
WgErmJOV6wg3BuRkrgZLUUnh57BYzhIzvBFdpadHRsc43UcjtZEevRGDIDu3YxocXMXe8O+xQpMRxwTJPv766IaNqUiUEjAIjZSMEYCZ0pBursUYB+9nB4eqNUiAS2MJ9sR+Cj2iBf6G6KXLfp9K6dK7c0UED5XrJwbovY8X8pMyxktFTEaflp0e76ZywsCQvtqEtqNz9uYEyqmAANbsBwbwyWpkCC8H1kZN2fV3CYetW1CTPmWdPp3C18Yfh826NN4XlKu1VmUmea70PyjmRKSsjPXpfrRX8udelVIK2WTFtnRxD4x588d1nlGY5D5DQmJ8KYZzfvjTmDXGAPiRIEGuXp8h6rBQXS8P/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
Below, you will find some examples demonstrating how to decrypt the webhook notification:
- C#
- Java
- PHP
- Python
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);
}
}
}
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();
}
}
}
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);
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)