Skip to content

Commit b027ee6

Browse files
authored
Merge pull request #1487 from Adyen/hmac-calculate-script
HMAC Troubleshooting tool
2 parents 7cc1838 + 768f287 commit b027ee6

File tree

7 files changed

+305
-2
lines changed

7 files changed

+305
-2
lines changed

src/utils/hmacValidator.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ class HmacValidator {
3535
*/
3636
public calculateHmac(data: string | NotificationRequestItem, key: string): string {
3737
const dataString = typeof data !== "string" ? this.getDataToSign(data) : data;
38-
const rawKey = Buffer.from(key, "hex");
39-
return createHmac(HmacValidator.HMAC_SHA256_ALGORITHM, rawKey).update(dataString, "utf8").digest("base64");
38+
return this.calculateHmacSignature(dataString, key);
4039
}
4140

4241
/**
@@ -131,6 +130,13 @@ class HmacValidator {
131130
return [...keys, ...values].join(HmacValidator.DATA_SEPARATOR);
132131
}
133132
}
133+
134+
public calculateHmacSignature(data: string, key: string): string {
135+
const rawKey = Buffer.from(key, "hex");
136+
return createHmac(HmacValidator.HMAC_SHA256_ALGORITHM, rawKey).update(data, "utf8").digest("base64");
137+
}
138+
134139
}
135140

141+
136142
export default HmacValidator;

tools/hmac/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
## HMAC Tools
2+
3+
This folder contains the scripts/tool to calculate the HMAC signature of the webhook payload.
4+
They can be used to troubleshoot the HMAC signature calculation. See [Webhooks documentation](https://docs.adyen.com/development-resources/webhooks/) page.
5+
6+
Check `tools/hmac/package.json` to confirm the Adyen Node API library version
7+
8+
Note: make sure you are using the HMAC key used to generate the signature associated with the payload in the JSON file
9+
10+
### Payments webhooks
11+
12+
Copy the content of the webhook in the payload.json (or provide a different file), then run with:
13+
`node calculateHmacPayments.js {hmacKey} {path to JSON file}`
14+
```
15+
cd tools/hmac
16+
npm install
17+
18+
npm list @adyen/api-library // check version of library and update if needed
19+
20+
node calculateHmacPayments.js 11223344D785FBAE710E7F943F307971BB61B21281C98C9129B3D4018A57B2EB payload.json
21+
```
22+
23+
### Platform webhooks (AfP, Management API, etc..)
24+
25+
Copy the content of the webhook in the payload2.json (or provide a different file), then run with:
26+
`node calculateHmacPlatform.js {hmacKey} {path to JSON file}`
27+
```
28+
cd tools/hmac
29+
npm install
30+
31+
npm list @adyen/api-library // check version of library and update if needed
32+
33+
node calculateHmacPlatform.js 11223344D785FBAE710E7F943F307971BB61B21281C98C9129B3D4018A57B2EB payload2.json
34+
```

tools/hmac/calculateHmacPayments.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// script to calculate the HMAC signature of Payments webhooks (where the signature is calculated considering
2+
// a subset of the fields in the payload - i.e. NotificationRequestItem object)
3+
// Note: HMAC signature is found in the AdditionalData object of the request payload
4+
//
5+
// Run with: `node calculateHmacPayments.js {hmacKey} {path to JSON file}
6+
//
7+
// cd tools/hmac
8+
// node calculateHmacPayments.js 11223344D785FBAE710E7F943F307971BB61B21281C98C9129B3D4018A57B2EB payload.json
9+
10+
const fs = require('fs');
11+
const { hmacValidator } = require('@adyen/api-library');
12+
13+
// Ensure correct arguments
14+
if (process.argv.length !== 4) {
15+
console.error("Usage: node calculateHmacPayments.js <hmacKey> <payloadFile>");
16+
process.exit(1);
17+
}
18+
19+
const hmacKey = process.argv[2];
20+
const payloadFile = process.argv[3];
21+
22+
// Check if file exists
23+
if (!fs.existsSync(payloadFile)) {
24+
console.error(`Error: File '${payloadFile}' not found.`);
25+
process.exit(1);
26+
}
27+
28+
console.log(`Calculating HMAC signature...`);
29+
30+
// Load and parse JSON file
31+
let jsonData;
32+
let payload;
33+
34+
try {
35+
payload = fs.readFileSync(payloadFile, 'utf8');
36+
jsonData = JSON.parse(payload);
37+
} catch (error) {
38+
console.error("Error: Invalid JSON in payload file.");
39+
process.exit(1);
40+
}
41+
42+
// Validate JSON structure
43+
if (!jsonData.notificationItems || !Array.isArray(jsonData.notificationItems)) {
44+
console.error("Error: 'notificationItems' key is missing or not an array.");
45+
process.exit(1);
46+
}
47+
48+
// Extract the first (and only) NotificationRequestItem
49+
const notificationRequestItem = jsonData.notificationItems[0]?.NotificationRequestItem;
50+
51+
if (!notificationRequestItem) {
52+
console.error("Error: 'NotificationRequestItem' is not found.");
53+
process.exit(1);
54+
}
55+
56+
//console.log(notificationRequestItem)
57+
58+
const validator = new hmacValidator()
59+
const hmacSignature = validator.calculateHmac(notificationRequestItem, hmacKey);
60+
61+
console.log('********');
62+
console.log(`Payload file: ${payloadFile}`);
63+
console.log(`Payload length: ${payload.length} characters`);
64+
/*
65+
// uncomment if needed
66+
const newlineCount = (payload.match(/\n/g) || []).length;
67+
const spaceCount = (payload.match(/ /g) || []).length;
68+
69+
console.log(`Newline count: ${newlineCount}`);
70+
console.log(`Space count: ${spaceCount}`);
71+
*/
72+
console.log('********');
73+
console.log(`HMAC signature: ${hmacSignature}`);
74+
process.exit(0);

tools/hmac/calculateHmacPlatform.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// script to calculate the HMAC signature of Banking/Management webhooks (where the signature is calculated over the
2+
// entire webhook payload)
3+
//
4+
// Run with: `node calculateHmacPlatform.js {hmacKey} {path to JSON file}`
5+
//
6+
// cd tools/hmac
7+
// node calculateHmacPlatform.js 11223344D785FBAE710E7F943F307971BB61B21281C98C9129B3D4018A57B2EB payload2.json
8+
9+
const fs = require('fs');
10+
const { hmacValidator } = require('@adyen/api-library');
11+
12+
13+
// Ensure correct arguments
14+
if (process.argv.length !== 4) {
15+
console.error("Usage: node calculateHmacPlatform.js <hmacKey> <payloadFile>");
16+
process.exit(1);
17+
}
18+
19+
const hmacKey = process.argv[2];
20+
const payloadFile = process.argv[3];
21+
22+
// Check if file exists
23+
if (!fs.existsSync(payloadFile)) {
24+
console.error(`Error: File '${payloadFile}' not found.`);
25+
process.exit(1);
26+
}
27+
28+
console.log(`Calculating HMAC signature...`);
29+
30+
// Load payload as raw string
31+
let payload;
32+
33+
try {
34+
payload = fs.readFileSync(payloadFile, 'utf8');
35+
} catch (error) {
36+
console.error(`Error reading file: ${error.message}`);
37+
process.exit(1);
38+
}
39+
40+
const validator = new hmacValidator()
41+
const hmacSignature = validator.calculateHmac(payload, hmacKey);
42+
43+
console.log('********');
44+
console.log(`Payload file: ${payloadFile}`);
45+
console.log(`Payload length: ${payload.length} characters`);
46+
/*
47+
// uncomment if needed to log number of new lines and number of spaces: this can be useful to confirm the payload sent by Adyen
48+
// is the same as the one you receive/parse
49+
50+
const newlineCount = (payload.match(/\n/g) || []).length;
51+
const spaceCount = (payload.match(/ /g) || []).length;
52+
53+
console.log(`Newline count: ${newlineCount}`);
54+
console.log(`Space count: ${spaceCount}`);
55+
*/
56+
console.log('********');
57+
58+
console.log(`HMAC signature: ${hmacSignature}`);
59+
process.exit(0);

tools/hmac/package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "hmac-troubleshooting",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"scripts": {
6+
"test": "echo \"Error: no test specified\" && exit 1"
7+
},
8+
"keywords": [],
9+
"author": "",
10+
"license": "ISC",
11+
"description": "Scripts to troubleshooting HMAC signature calculation",
12+
"dependencies": {
13+
"@adyen/api-library": "^25.0.0"
14+
}
15+
}

tools/hmac/payload.json

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"live": "false",
3+
"notificationItems": [
4+
{
5+
"NotificationRequestItem": {
6+
"additionalData": {
7+
"cardSummary": "0010",
8+
"hmacSignature": "e7qQumH1fdt5NHb6e62jq6hIGUCpfhAAFXhUXqXcJAQ=",
9+
"expiryDate": "03\/2030",
10+
"recurring.contractTypes": "ONECLICK,RECURRING",
11+
"cardBin": "222241",
12+
"recurring.recurringDetailReference": "DQWW2BQ9FX2R7475",
13+
"recurringProcessingModel": "Subscription",
14+
"metadata.orderTransactionId": "63",
15+
"alias": "F758505419855455",
16+
"paymentMethodVariant": "m1",
17+
"acquirerReference": "JQBMF3P3FJM",
18+
"issuerBin": "22224107",
19+
"cardIssuingCountry": "NL",
20+
"authCode": "060494",
21+
"cardHolderName": "Checkout Test",
22+
"PaymentAccountReference": "Jj3gVfMpPcpKzmHrHmpxqgfsvGaVa",
23+
"checkout.cardAddedBrand": "mc",
24+
"store": "None",
25+
"tokenization.shopperReference": "4d74f0727a318b13f85aee28ae556a08d7d9cf9e6d67b70a62e60e334aa7090d",
26+
"recurring.firstPspReference": "P9M9CTGJKKKKP275",
27+
"tokenization.storedPaymentMethodId": "DQWW2BQ9FX2R7475",
28+
"issuerCountry": "NL",
29+
"aliasType": "Default",
30+
"paymentMethod": "mc",
31+
"recurring.shopperReference": "4d74f0727a318b13f85aee28ae556a08d7d9cf9e6d67b70a62e60e334aa7090d",
32+
"cardPaymentMethod": "mc2"
33+
},
34+
"amount": {
35+
"currency": "EUR",
36+
"value": 32500
37+
},
38+
"eventCode": "AUTHORISATION",
39+
"eventDate": "2025-02-24T15:20:56+01:00",
40+
"merchantAccountCode": "Merchant 1",
41+
"merchantReference": "1143-R2",
42+
"operations": [
43+
"CANCEL",
44+
"CAPTURE",
45+
"REFUND"
46+
],
47+
"paymentMethod": "mc",
48+
"pspReference": "AB1234567890",
49+
"reason": "060494:0010:03\/2030",
50+
"success": "true"
51+
}
52+
}
53+
]
54+
}

tools/hmac/payload2.json

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"data": {
3+
"balancePlatform": "YOUR_BALANCE_PLATFORM",
4+
"accountHolder": {
5+
"contactDetails": {
6+
"email": "[email protected]",
7+
"phone": {
8+
"number": "0612345678",
9+
"type": "mobile"
10+
},
11+
"address": {
12+
"houseNumberOrName": "23",
13+
"city": "Amsterdam",
14+
"country": "NL",
15+
"postalCode": "12345",
16+
"street": "Main Street 1"
17+
}
18+
},
19+
"description": "Shelly Eller",
20+
"legalEntityId": "LE00000000000000000001",
21+
"reference": "YOUR_REFERENCE-2412C",
22+
"capabilities": {
23+
"issueCard": {
24+
"enabled": true,
25+
"requested": true,
26+
"allowed": false,
27+
"verificationStatus": "pending"
28+
},
29+
"receiveFromTransferInstrument": {
30+
"enabled": true,
31+
"requested": true,
32+
"allowed": false,
33+
"verificationStatus": "pending"
34+
},
35+
"sendToTransferInstrument": {
36+
"enabled": true,
37+
"requested": true,
38+
"allowed": false,
39+
"verificationStatus": "pending"
40+
},
41+
"sendToBalanceAccount": {
42+
"enabled": true,
43+
"requested": true,
44+
"allowed": false,
45+
"verificationStatus": "pending"
46+
},
47+
"receiveFromBalanceAccount": {
48+
"enabled": true,
49+
"requested": true,
50+
"allowed": false,
51+
"verificationStatus": "pending"
52+
}
53+
},
54+
"id": "AH00000000000000000001",
55+
"status": "active"
56+
}
57+
},
58+
"environment": "test",
59+
"timestamp": "2024-12-15T15:42:03+01:00",
60+
"type": "balancePlatform.accountHolder.created"
61+
}

0 commit comments

Comments
 (0)