Signature verification

Verifying the signature in webhook notifications is crucial for ensuring the security and integrity of the data received from Pleenk.

This process confirms that the webhook notification genuinely comes from Pleenk and has not been tampered with during transmission.

By validating the signature, you protect your marketplace from potential security threats, such as spoofed requests or data breaches, ensuring that only legitimate notifications are processed.

This verification step is essential to maintain trust and reliability in your integration with the Pleenk platform.

Signature verification steps

The signature verification process ensures data integrity by extracting the signature and raw data, loading the public key, decoding the signature, and verifying it against the data. The result confirms whether the signature is valid, invalid, or if an error occurred.

  1. Retrieve the Signature: The signature is extracted from an HTTP header or a similar source (in this case, it is directly assigned to a variable for illustration purposes).
  2. Retrieve the Raw Data: The raw data is retrieved (e.g., from a POST request) and converted to UTF-8 encoding.
  3. Load the Public Key: The public key is defined in PEM format and loaded into a suitable resource for verification.
  4. Decode the Base64 Signature: The base64-encoded signature is decoded to prepare it for verification.
  5. Verify the Signature: A verification function is used to check if the signature matches the data using the provided public key.

Pleenk public key

To ensure the integrity and authenticity of the data, please use the following public key for signature verification.

-----BEGIN PUBLIC KEY-----
  MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBeXWq/pX+pp5jlFkzL/UlACqaJ8
  mA5arZlI9guMKFgktBgaGPn/uo4Xj1dtOs3SFfzl1qo76k5e+K3awsKa5J5EsB
  5KSYWMzjTae3IQgxCCPop70V/82ld4Iwe4hsJsOnirHnsQa8TwmBC0+80fasCX
  Xjpq9jKiZ5OLcjxcK9B5ojDZU=
 -----END PUBLIC KEY-----
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA8vcy72W3PbALsobv+ihXAbBHhzxV
Gf6nb+Q+KR2ysuSC3SVtNsMrE+mkdVUPhhW84ZSXq2vzoA0FCTw7Faz6SnoAMb0D
izd47yANu1E0zYja8+FX0d1TGsvqTXNQamZbqTAMi0xM29BHqomuZZ8L1eJzh/Z6
IroWGTPLdk8s1uO4pnE=
-----END PUBLIC KEY-----

Example

import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class SignatureVerification {
    public static void main(String[] args) {
        try {
            // Step 1: Retrieve signature from header
            String signatureStr = "MIGIAkIA2wrAKMdssBwb0RmAnoFYM5jmOYb0LGyWisgc6BcXRV73pi1SqLmOXVA6FAPX9DmtjOo_DWyoq9URORhAUU_ySzACQgFQIWelrgS4FiTSyag3mN3n_ZWxGFn3lS3B3sOKAMW0f4eEgs7kBXVsm4H-rit7lUSFe7fiHDlT2_ygvSGu-QXBBw==";

            // Step 2: Retrieve raw data from POST
            String dataStr = "{\"events\":[{\"eventType\":\"PAYMENT\",\"data\":{\"@type\":\"PAYMENT_EVENT\",\"transactionRef\":\"95-1719218863\",\"paymentId\":\"cac8034f-8da8-4c3f-bde6-19e33d4f4990\",\"privacyScope\":\"f1f35e28-1b8d-4af5-b6d5-7212ad3a4f04\",\"metadata\":\"95\",\"status\":\"CONFIRMED\",\"beneficiaries\":[]}}]}";
            byte[] data = dataStr.getBytes(StandardCharsets.UTF_8);

            // Step 3: Load Pleenk public key
            String pleenkPublicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" +
                    "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA8vcy72W3PbALsobv+ihXAbBHhzxV\n" +
                    "Gf6nb+Q+KR2ysuSC3SVtNsMrE+mkdVUPhhW84ZSXq2vzoA0FCTw7Faz6SnoAMb0D\n" +
                    "izd47yANu1E0zYja8+FX0d1TGsvqTXNQamZbqTAMi0xM29BHqomuZZ8L1eJzh/Z6\n" +
                    "IroWGTPLdk8s1uO4pnE=\n" +
                    "-----END PUBLIC KEY-----";
            String publicKeyPEM = pleenkPublicKeyPEM.replace("-----BEGIN PUBLIC KEY-----", "")
                    .replace("-----END PUBLIC KEY-----", "").replaceAll("\\s", "");

            byte[] encodedPublicKey = Base64.getDecoder().decode(publicKeyPEM);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedPublicKey);
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
            PublicKey publicKey = keyFactory.generatePublic(keySpec);

            // Step 4: Decode signature from base64
            byte[] signature = Base64.getUrlDecoder().decode(signatureStr);

            // Step 5: Check signature
            Signature sig = Signature.getInstance("SHA512withECDSA");
            sig.initVerify(publicKey);
            sig.update(data);
            boolean isValid = sig.verify(signature);

            // Step 6: Display verification result
            if (isValid) {
                System.out.println("Signature is valid.");
            } else {
                System.out.println("Signature is invalid.");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// Create signature service with your marketplace private key
PleenkSignatureService signatureService = new PleenkSignatureService(marketplaceKeyPairPrivateKey);

// Create the notification parser with the signature service
NotificationParser notificationParser = new NotificationParser(signatureService);

// Extract and verify the notification from the HTTP request
PleenkWebhookPayload notification = notificationParser.extractAndVerify(httpServletRequest);

// Get the list of events from the notification payload
List<PleenkWebhookEvent<? extends PleenkWebhookData>> events = notification.getEvents();
<?php

// Step 1 : Retrieve signature from header
$signature = 'MIGIAkIA2wrAKMdssBwb0RmAnoFYM5jmOYb0LGyWisgc6BcXRV73pi1SqLmOXVA6FAPX9DmtjOo_DWyoq9URORhAUU_ySzACQgFQIWelrgS4FiTSyag3mN3n_ZWxGFn3lS3B3sOKAMW0f4eEgs7kBXVsm4H-rit7lUSFe7fiHDlT2_ygvSGu-QXBBw==';

// Step 2 : Retrieve raw data from POST
$data = '{"events":[{"eventType":"PAYMENT","data":{"@type":"PAYMENT_EVENT","transactionRef":"95-1719218863","paymentId":"cac8034f-8da8-4c3f-bde6-19e33d4f4990","privacyScope":"f1f35e28-1b8d-4af5-b6d5-7212ad3a4f04","metadata":"95","status":"CONFIRMED","beneficiaries":[]}}]}';

// Convert to UTF-8
$data = mb_convert_encoding($data, 'UTF-8');

// Step 3 : Load Pleenk public key
$pleenkPublicKey = openssl_pkey_get_public("-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA8vcy72W3PbALsobv+ihXAbBHhzxV
Gf6nb+Q+KR2ysuSC3SVtNsMrE+mkdVUPhhW84ZSXq2vzoA0FCTw7Faz6SnoAMb0D
izd47yANu1E0zYja8+FX0d1TGsvqTXNQamZbqTAMi0xM29BHqomuZZ8L1eJzh/Z6
IroWGTPLdk8s1uO4pnE=
-----END PUBLIC KEY-----");

// Step 4 : Decode signature to base64
$signature = base64_decode(strtr($signature, '-_', '+/'));

// Step 5 : Check signature
$result = openssl_verify($data, $signature, $pleenkPublicKey, OPENSSL_ALGO_SHA512);

// Step 6 : Display verification result
if ($result == 1) {
    echo "Signature is valid.";
} elseif ($result == 0) {
    echo "Signature is invalid. ".$result;
} else {
    echo "Error verifying signature: ".$result .' '. openssl_error_string();
}
?>
const crypto = require('crypto');

// Step 1: Retrieve signature from header
let signature = 'MIGIAkIA2wrAKMdssBwb0RmAnoFYM5jmOYb0LGyWisgc6BcXRV73pi1SqLmOXVA6FAPX9DmtjOo_DWyoq9URORhAUU_ySzACQgFQIWelrgS4FiTSyag3mN3n_ZWxGFn3lS3B3sOKAMW0f4eEgs7kBXVsm4H-rit7lUSFe7fiHDlT2_ygvSGu-QXBBw==';

// Step 2: Retrieve raw data from POST
let data = '{"events":[{"eventType":"PAYMENT","data":{"@type":"PAYMENT_EVENT","transactionRef":"95-1719218863","paymentId":"cac8034f-8da8-4c3f-bde6-19e33d4f4990","privacyScope":"f1f35e28-1b8d-4af5-b6d5-7212ad3a4f04","metadata":"95","status":"CONFIRMED","beneficiaries":[]}}]}';

// Convert to UTF-8
data = Buffer.from(data, 'utf-8');

// Step 3: Load Pleenk public key
const pleenkPublicKey = `-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA8vcy72W3PbALsobv+ihXAbBHhzxV
Gf6nb+Q+KR2ysuSC3SVtNsMrE+mkdVUPhhW84ZSXq2vzoA0FCTw7Faz6SnoAMb0D
izd47yANu1E0zYja8+FX0d1TGsvqTXNQamZbqTAMi0xM29BHqomuZZ8L1eJzh/Z6
IroWGTPLdk8s1uO4pnE=
-----END PUBLIC KEY-----`;

// Step 4: Decode signature from base64
signature = Buffer.from(signature.replace(/-/g, '+').replace(/_/g, '/'), 'base64');

// Step 5: Check signature
const verify = crypto.createVerify('SHA512');
verify.update(data);
verify.end();

const isValid = verify.verify(pleenkPublicKey, signature);

// Step 6: Display verification result
if (isValid) {
    console.log("Signature is valid.");
} else {
    console.log("Signature is invalid.");
}
import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import load_pem_public_key

# Step 1: Retrieve signature from header
signature_str = 'MIGIAkIA2wrAKMdssBwb0RmAnoFYM5jmOYb0LGyWisgc6BcXRV73pi1SqLmOXVA6FAPX9DmtjOo_DWyoq9URORhAUU_ySzACQgFQIWelrgS4FiTSyag3mN3n_ZWxGFn3lS3B3sOKAMW0f4eEgs7kBXVsm4H-rit7lUSFe7fiHDlT2_ygvSGu-QXBBw=='

# Step 2: Retrieve raw data from POST
data = '{"events":[{"eventType":"PAYMENT","data":{"@type":"PAYMENT_EVENT","transactionRef":"95-1719218863","paymentId":"cac8034f-8da8-4c3f-bde6-19e33d4f4990","privacyScope":"f1f35e28-1b8d-4af5-b6d5-7212ad3a4f04","metadata":"95","status":"CONFIRMED","beneficiaries":[]}}]}'.encode('utf-8')

# Step 3: Load Pleenk public key
pleenk_public_key_pem = b"""-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA8vcy72W3PbALsobv+ihXAbBHhzxV
Gf6nb+Q+KR2ysuSC3SVtNsMrE+mkdVUPhhW84ZSXq2vzoA0FCTw7Faz6SnoAMb0D
izd47yANu1E0zYja8+FX0d1TGsvqTXNQamZbqTAMi0xM29BHqomuZZ8L1eJzh/Z6
IroWGTPLdk8s1uO4pnE=
-----END PUBLIC KEY-----"""

public_key = load_pem_public_key(pleenk_public_key_pem)

# Step 4: Decode signature from base64
signature = base64.urlsafe_b64decode(signature_str + '==')

# Step 5: Check signature
try:
    public_key.verify(signature, data, ec.ECDSA(hashes.SHA512()))
    print("Signature is valid.")
except Exception as e:
    print("Signature is invalid:", str(e))