Skip to content
Menu

PAYMENT GATEWAY

[THK] E.1.5 Security and Validation

Overview

Webhook notifications from SIBS Payment Gateway (SPG) contain sensitive transaction data and are transmitted in encrypted form to protect against fraud and tampering.

To process webhook notifications correctly, the merchant system must:

  • Decrypt the payload
  • Validate its integrity and authenticity
  • Ensure secure handling of all cryptographic material

This section describes the required mechanisms to securely handle webhook notifications.

Security Model

SPG webhook security is based on the following principles:

  • Confidentiality – payload is encrypted
  • Integrity – payload is protected against tampering
  • Authenticity – payload originates from SPG
  • Replay protection – duplicate or malicious reuse must be handled

Encrypted Webhook Payloads

Webhook payloads are not delivered as plain JSON.

They are transmitted in an encrypted format, where:

  • The request body contains the encrypted payload (Base64 encoded)
  • Additional cryptographic parameters are provided via HTTP headers

After decryption, the resulting payload corresponds to the standard webhook structure described in this documentation.

Encryption and Decryption Specifications

Algorithm

  • Encryption algorithm: AES
  • Block mode: GCM
  • Padding: None

Encoding Rules

  • Encrypted payload (body): Base64 encoded
  • Initialization Vector (IV): Base64 encoded
  • Text encoding: UTF-8 (when converting between string and byte representation)

Required Headers

The following HTTP headers are required to decrypt the webhook payload:

  • X-Initialization-Vector → Initialization Vector (Base64)
  • X-Authentication-Tag → Authentication tag (Base64)

These values must be used together with the secret key to perform AES-GCM decryption.

Secret Key Management

Source of the Secret

The secret key must be obtained from the SPG Backoffice, in the webhook configuration section.

Info

The configuration process and retrieval of the secret are described in E.1.7 Webhook Configuration (SPG Backoffice).

Usage

The secret key is used to:

  • Decrypt the webhook payload
  • Validate the authenticity and integrity of the message (via GCM authentication tag)

Best Practices

  • Store the secret in a secure vault or key management system
  • Never expose the secret in logs or client-side code
  • Restrict access to authorized systems only
  • Rotate the secret if supported

Decryption Process

Webhook notifications are received in encrypted form and must be decrypted using the parameters provided in the HTTP request.

General Flow

Encrypted Body

  • The request body contains the encrypted payload, encoded in Base64
  • After Base64 decoding, the result corresponds to the ciphertext to be decrypted

Required HTTP Headers

The decryption process depends on the following headers included in the webhook request:

X-Initialization-Vector
  • Contains the Initialization Vector (IV) used in the encryption process
  • Encoding: Base64
  • Must be decoded before being used in the decryption algorithm
X-Authentication-Tag
  • Contains the authentication tag generated by the AES-GCM algorithm
  • Used to verify:
    • Data integrity
    • Authenticity of the payload
  • Encoding: Base64
  • Must be decoded and provided to the decryption operation

Decryption Inputs

To perform decryption, the following inputs are required:

  • Secret key (configured in SPG Backoffice)
  • Encrypted payload (request body, Base64 decoded)
  • Initialization Vector (from X-Initialization-Vector, Base64 decoded)
  • Authentication Tag (from X-Authentication-Tag, Base64 decoded)

Decryption Operation

The merchant system must:

  1. Decode all Base64-encoded inputs
  2. Apply AES decryption using:
    • Mode: GCM
    • Padding: None
  3. Provide:
    • Key
    • IV
    • Authentication Tag
  4. Obtain the original JSON payload (UTF-8 encoded)

Example Decryption Implementation

Info

The following examples illustrate how to perform AES-GCM decryption using the headers and payload received in the webhook request.

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)

Important Notes

  • If authentication tag validation fails, the payload must be considered invalid
  • The payload must not be processed if decryption is unsuccessful
  • All Base64 values must be decoded before use
  • All string-to-byte conversions must use UTF-8 encoding

Payload Validation

After decryption, the payload must be validated.

Validation Steps

  • Ensure valid JSON format
  • Validate presence of critical fields:
    • transactionID
    • paymentStatus
  • Verify logical consistency

If decryption fails:

  • The payload must be considered invalid
  • Processing must be aborted

Replay Protection and Idempotency

Webhook notifications may be retried or duplicated.

Recommended Protections

  • Use transactionID as idempotency key
  • Store processed transactions
  • Ignore duplicates or ensure safe reprocessing

Transport Security

Webhook endpoints must enforce secure communication.

Requirements

  • HTTPS only
  • TLS 1.2 or higher
  • Valid SSL certificate

Error Handling in Decryption

Typical Failure Scenarios

  • Invalid secret key
  • Incorrect IV or authentication tag
  • Corrupted payload
  • Authentication failure (GCM tag mismatch)

Recommended Behavior

  • Log the error (without exposing sensitive data)
  • Do not process the payload
  • Allow retry if appropriate

Common Decryption Errors

The following issues are commonly observed during webhook integration and may prevent successful decryption or validation of the payload.

Incorrect AES Mode

  • Using AES-CBC or other modes instead of AES-GCM

Impact:

  • Decryption may succeed incorrectly or fail entirely
  • Authentication tag validation is not performed

Missing or Invalid Authentication Tag

  • Not providing the X-Authentication-Tag header
  • Using an incorrectly decoded tag

Impact:

  • Decryption fails with authentication error
  • Payload integrity cannot be verified

Incorrect Initialization Vector (IV)

  • Not decoding Base64 correctly
  • Using an incorrect IV length or value

Impact:

  • Decryption fails
  • Output is invalid or corrupted

Invalid Secret Key

  • Using a wrong or outdated secret
  • Incorrect key encoding (e.g., treating Base64 as plain text)

Impact:

  • Decryption fails
  • Authentication tag validation fails

Incorrect Base64 Handling

  • Not decoding the request body from Base64
  • Double decoding or incorrect character encoding

Impact:

  • Ciphertext is invalid
  • Decryption produces incorrect results

Ignoring UTF-8 Encoding

  • Using incorrect string encoding when converting decrypted bytes

Impact:

  • Payload appears corrupted
  • JSON parsing fails

Processing Payload Without Valid Decryption

  • Attempting to parse or use payload before verifying decryption success

Impact:

  • Invalid data processed
  • Security vulnerabilities

Not Handling Decryption Failures Properly

  • Failing silently
  • Not logging errors
  • Accepting invalid payloads

Impact:

  • Loss of observability
  • Potential data inconsistency or security risks

Recommendation

Warning

If decryption fails or authentication cannot be verified, the payload must be considered invalid and must not be processed.

Ensure that:

  • All cryptographic inputs are correctly decoded
  • AES-GCM is used as specified
  • Errors are logged for troubleshooting
  • Invalid payloads are safely discarded

Common Security Pitfalls

  • Using incorrect AES mode (e.g., CBC instead of GCM)
  • Ignoring authentication tag validation
  • Not decoding Base64 inputs correctly
  • Logging decrypted sensitive data
  • Hardcoding secret keys
  • Skipping payload validation

Relationship with Webhook Processing

Security and validation are part of the webhook handling pipeline.

The correct sequence is:

Notification

Webhook acknowledgement must follow the rules defined in E.1.3 Webhook Delivery, Retries and Idempotency, including the requirement to return HTTP 200 OK with the expected acknowledgement response body.

Summary

Webhook security in SPG requires:

  • Decrypting payloads using AES-GCM with Base64-encoded inputs
  • Using IV and authentication tag provided in HTTP headers
  • Managing the secret key obtained from the SPG Backoffice
  • Validating payload integrity before processing
  • Ensuring secure transport and idempotent handling

A correct implementation ensures secure, reliable, and production-grade webhook processing.

Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.

Strictly Necessary Cookies

Strictly Necessary Cookie should be enabled at all times so that we can save your preferences for cookie settings.