Verify Signature
Verify the authenticity of incoming webhook payloads by validating the signature attached to each request. This ensures the payload was sent by Amwal and has not been tampered with in transit.
The verification uses RSA-PSS with SHA-256 — an asymmetric signing scheme where Amwal signs payloads with a private key, and you verify them using the corresponding public key.
Prerequisites
- Retrieve your RSA public key (PEM format) from the Create Webhook and API Key endpoint.
- Extract the signature from the
x-signatureheader of each incoming webhook request.
Inputs
Verification requires three values: the RSA public key, the webhook payload, and the Base64-encoded signature. Replace the placeholders below with your actual values.
const publicKeyPem = `-----BEGIN PUBLIC KEY-----
-----END PUBLIC KEY-----`;
const payload = {
"amount": "29.00000",
"client_first_name": "fname ",
"client_last_name": "lname",
"payment_link_id": "9493559e-yyyyyyb",
"payment_option": "Pay In Full",
"status": "success",
"transaction_id": "ccdaaba6-xxxxx5"
};
const signatureBase64 = "amwal - signature";Verify the Signature
The verification process follows these steps:
-
Parse the PEM key — Strip the PEM headers and decode the Base64 content into a binary key that the Web Crypto API can import.
-
Import the public key — Import the binary key as an RSA-PSS key with SHA-256 hashing.
-
Serialize the payload — Extract only the following fields from the incoming payload, sort the keys alphabetically, and serialize to a JSON string with a space after each
:and,.Required fields:
amount,client_first_name,client_last_name,payment_link_id,payment_option,status,transaction_id{ "amount": "29.00000", "client_first_name": "fname ", "client_last_name": "lname", "payment_link_id": "9493559e-yyyyyy", "payment_option": "Pay In Full", "status": "success", "transaction_id": "ccdaaba6-xxxxx" }Include only the fields listed above — do not add, remove, or rename any fields. Serialize with UTF-8 encoding, consistent key ordering (alphabetical), and exact string values (including trailing spaces). Any difference in serialization will cause verification to fail.
-
Decode the signature — Convert the Base64-encoded signature string into a binary array.
-
Verify — Pass the key, signature, and serialized payload to
crypto.subtle.verifyusing RSA-PSS.
async function verifySignature() {
try {
// A. Clean the PEM string to get raw Base64
const pemContents = publicKeyPem
.replace(/-----BEGIN PUBLIC KEY-----/g, "")
.replace(/-----END PUBLIC KEY-----/g, "")
.replace(/\s/g, "");
// B. Convert PEM Base64 to ArrayBuffer
const binaryDerString = window.atob(pemContents);
const binaryDer = new Uint8Array(binaryDerString.length);
for (let i = 0; i < binaryDerString.length; i++) {
binaryDer[i] = binaryDerString.charCodeAt(i);
}
// C. Import the Public Key
const cryptoKey = await window.crypto.subtle.importKey(
"spki",
binaryDer.buffer,
{ name: "RSA-PSS", hash: "SHA-256" },
false,
["verify"]
);
const sortedKeys = Object.keys(payload).sort();
const sortedObj = {};
sortedKeys.forEach(key => sortedObj[key] = payload[key]);
const jsonString = JSON.stringify(sortedObj, null, 0)
.replace(/:/g, ": ")
.replace(/,/g, ", ");
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(jsonString);
// E. Decode Signature Base64
const sigBinary = Uint8Array.from(atob(signatureBase64), c => c.charCodeAt(0));
// F. Verify using RSA-PSS
const isValid = await window.crypto.subtle.verify(
{
name: "RSA-PSS",
saltLength: 222, // SHA-256 PSS usually uses 32-byte salt, or max.
},
cryptoKey,
sigBinary,
dataBuffer
);
if (isValid) {
alert( "✅ Verification Successful!");
} else {
const dataBufferSimple = encoder.encode(JSON.stringify(sortedObj));
const isValidSimple = await window.crypto.subtle.verify(
{ name: "RSA-PSS", saltLength: 32 },
cryptoKey, sigBinary, dataBufferSimple
);
if (isValidSimple) {
alert( "✅ Verification Successful (Simple JSON)!");
} else {
alert( "❌ Verification Failed: Signature mismatch.");
}
}
} catch (err) {
alert( "❌ Error: " + err.message);
}
}
Verification Outcomes
| Result | Meaning | Action |
|---|---|---|
| ✅ Verification Successful | The payload is authentic and unmodified. | Process the webhook event. |
| ❌ Signature mismatch | The signature does not match the payload. The request may be forged or the payload was altered. | Reject the request. Do not process the event. |
Updated 2 days ago