Skip to content

HMAC Troubleshooting tool #1487

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/utils/hmacValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ class HmacValidator {
*/
public calculateHmac(data: string | NotificationRequestItem, key: string): string {
const dataString = typeof data !== "string" ? this.getDataToSign(data) : data;
const rawKey = Buffer.from(key, "hex");
return createHmac(HmacValidator.HMAC_SHA256_ALGORITHM, rawKey).update(dataString, "utf8").digest("base64");
return this.calculateHmacSignature(dataString, key);
}

/**
Expand Down Expand Up @@ -131,6 +130,13 @@ class HmacValidator {
return [...keys, ...values].join(HmacValidator.DATA_SEPARATOR);
}
}

public calculateHmacSignature(data: string, key: string): string {
const rawKey = Buffer.from(key, "hex");
return createHmac(HmacValidator.HMAC_SHA256_ALGORITHM, rawKey).update(data, "utf8").digest("base64");
}

}


export default HmacValidator;
34 changes: 34 additions & 0 deletions tools/hmac/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## HMAC Tools

This folder contains the scripts/tool to calculate the HMAC signature of the webhook payload.
They can be used to troubleshoot the HMAC signature calculation. See [Webhooks documentation](https://docs.adyen.com/development-resources/webhooks/) page.

Check `tools/hmac/package.json` to confirm the Adyen Node API library version

Note: make sure you are using the HMAC key used to generate the signature associated with the payload in the JSON file

### Payments webhooks

Copy the content of the webhook in the payload.json (or provide a different file), then run with:
`node calculateHmacPayments.js {hmacKey} {path to JSON file}`
```
cd tools/hmac
npm install

npm list @adyen/api-library // check version of library and update if needed

node calculateHmacPayments.js 11223344D785FBAE710E7F943F307971BB61B21281C98C9129B3D4018A57B2EB payload.json
```

### Platform webhooks (AfP, Management API, etc..)

Copy the content of the webhook in the payload2.json (or provide a different file), then run with:
`node calculateHmacPlatform.js {hmacKey} {path to JSON file}`
```
cd tools/hmac
npm install

npm list @adyen/api-library // check version of library and update if needed

node calculateHmacPlatform.js 11223344D785FBAE710E7F943F307971BB61B21281C98C9129B3D4018A57B2EB payload2.json
```
74 changes: 74 additions & 0 deletions tools/hmac/calculateHmacPayments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// script to calculate the HMAC signature of Payments webhooks (where the signature is calculated considering
// a subset of the fields in the payload - i.e. NotificationRequestItem object)
// Note: HMAC signature is found in the AdditionalData object of the request payload
//
// Run with: `node calculateHmacPayments.js {hmacKey} {path to JSON file}
//
// cd tools/hmac
// node calculateHmacPayments.js 11223344D785FBAE710E7F943F307971BB61B21281C98C9129B3D4018A57B2EB payload.json

const fs = require('fs');
const { hmacValidator } = require('@adyen/api-library');

// Ensure correct arguments
if (process.argv.length !== 4) {
console.error("Usage: node calculateHmacPayments.js <hmacKey> <payloadFile>");
process.exit(1);
}

const hmacKey = process.argv[2];
const payloadFile = process.argv[3];

// Check if file exists
if (!fs.existsSync(payloadFile)) {
console.error(`Error: File '${payloadFile}' not found.`);
process.exit(1);
}

console.log(`Calculating HMAC signature...`);

// Load and parse JSON file
let jsonData;
let payload;

try {
payload = fs.readFileSync(payloadFile, 'utf8');
jsonData = JSON.parse(payload);
} catch (error) {
console.error("Error: Invalid JSON in payload file.");
process.exit(1);
}

// Validate JSON structure
if (!jsonData.notificationItems || !Array.isArray(jsonData.notificationItems)) {
console.error("Error: 'notificationItems' key is missing or not an array.");
process.exit(1);
}

// Extract the first (and only) NotificationRequestItem
const notificationRequestItem = jsonData.notificationItems[0]?.NotificationRequestItem;

if (!notificationRequestItem) {
console.error("Error: 'NotificationRequestItem' is not found.");
process.exit(1);
}

//console.log(notificationRequestItem)

const validator = new hmacValidator()
const hmacSignature = validator.calculateHmac(notificationRequestItem, hmacKey);

console.log('********');
console.log(`Payload file: ${payloadFile}`);
console.log(`Payload length: ${payload.length} characters`);
/*
// uncomment if needed
const newlineCount = (payload.match(/\n/g) || []).length;
const spaceCount = (payload.match(/ /g) || []).length;

console.log(`Newline count: ${newlineCount}`);
console.log(`Space count: ${spaceCount}`);
*/
console.log('********');
console.log(`HMAC signature: ${hmacSignature}`);
process.exit(0);
59 changes: 59 additions & 0 deletions tools/hmac/calculateHmacPlatform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// script to calculate the HMAC signature of Banking/Management webhooks (where the signature is calculated over the
// entire webhook payload)
//
// Run with: `node calculateHmacPlatform.js {hmacKey} {path to JSON file}`
//
// cd tools/hmac
// node calculateHmacPlatform.js 11223344D785FBAE710E7F943F307971BB61B21281C98C9129B3D4018A57B2EB payload2.json

const fs = require('fs');
const { hmacValidator } = require('@adyen/api-library');


// Ensure correct arguments
if (process.argv.length !== 4) {
console.error("Usage: node calculateHmacPlatform.js <hmacKey> <payloadFile>");
process.exit(1);
}

const hmacKey = process.argv[2];
const payloadFile = process.argv[3];

// Check if file exists
if (!fs.existsSync(payloadFile)) {
console.error(`Error: File '${payloadFile}' not found.`);
process.exit(1);
}

console.log(`Calculating HMAC signature...`);

// Load payload as raw string
let payload;

try {
payload = fs.readFileSync(payloadFile, 'utf8');
} catch (error) {
console.error(`Error reading file: ${error.message}`);
process.exit(1);
}

const validator = new hmacValidator()
const hmacSignature = validator.calculateHmac(payload, hmacKey);

console.log('********');
console.log(`Payload file: ${payloadFile}`);
console.log(`Payload length: ${payload.length} characters`);
/*
// uncomment if needed to log number of new lines and number of spaces: this can be useful to confirm the payload sent by Adyen
// is the same as the one you receive/parse

const newlineCount = (payload.match(/\n/g) || []).length;
const spaceCount = (payload.match(/ /g) || []).length;

console.log(`Newline count: ${newlineCount}`);
console.log(`Space count: ${spaceCount}`);
*/
console.log('********');

console.log(`HMAC signature: ${hmacSignature}`);
process.exit(0);
15 changes: 15 additions & 0 deletions tools/hmac/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "hmac-troubleshooting",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "Scripts to troubleshooting HMAC signature calculation",
"dependencies": {
"@adyen/api-library": "^25.0.0"
}
}
54 changes: 54 additions & 0 deletions tools/hmac/payload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"live": "false",
"notificationItems": [
{
"NotificationRequestItem": {
"additionalData": {
"cardSummary": "0010",
"hmacSignature": "e7qQumH1fdt5NHb6e62jq6hIGUCpfhAAFXhUXqXcJAQ=",
"expiryDate": "03\/2030",
"recurring.contractTypes": "ONECLICK,RECURRING",
"cardBin": "222241",
"recurring.recurringDetailReference": "DQWW2BQ9FX2R7475",
"recurringProcessingModel": "Subscription",
"metadata.orderTransactionId": "63",
"alias": "F758505419855455",
"paymentMethodVariant": "m1",
"acquirerReference": "JQBMF3P3FJM",
"issuerBin": "22224107",
"cardIssuingCountry": "NL",
"authCode": "060494",
"cardHolderName": "Checkout Test",
"PaymentAccountReference": "Jj3gVfMpPcpKzmHrHmpxqgfsvGaVa",
"checkout.cardAddedBrand": "mc",
"store": "None",
"tokenization.shopperReference": "4d74f0727a318b13f85aee28ae556a08d7d9cf9e6d67b70a62e60e334aa7090d",
"recurring.firstPspReference": "P9M9CTGJKKKKP275",
"tokenization.storedPaymentMethodId": "DQWW2BQ9FX2R7475",
"issuerCountry": "NL",
"aliasType": "Default",
"paymentMethod": "mc",
"recurring.shopperReference": "4d74f0727a318b13f85aee28ae556a08d7d9cf9e6d67b70a62e60e334aa7090d",
"cardPaymentMethod": "mc2"
},
"amount": {
"currency": "EUR",
"value": 32500
},
"eventCode": "AUTHORISATION",
"eventDate": "2025-02-24T15:20:56+01:00",
"merchantAccountCode": "Merchant 1",
"merchantReference": "1143-R2",
"operations": [
"CANCEL",
"CAPTURE",
"REFUND"
],
"paymentMethod": "mc",
"pspReference": "AB1234567890",
"reason": "060494:0010:03\/2030",
"success": "true"
}
}
]
}
61 changes: 61 additions & 0 deletions tools/hmac/payload2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"data": {
"balancePlatform": "YOUR_BALANCE_PLATFORM",
"accountHolder": {
"contactDetails": {
"email": "test@adyen.com",
"phone": {
"number": "0612345678",
"type": "mobile"
},
"address": {
"houseNumberOrName": "23",
"city": "Amsterdam",
"country": "NL",
"postalCode": "12345",
"street": "Main Street 1"
}
},
"description": "Shelly Eller",
"legalEntityId": "LE00000000000000000001",
"reference": "YOUR_REFERENCE-2412C",
"capabilities": {
"issueCard": {
"enabled": true,
"requested": true,
"allowed": false,
"verificationStatus": "pending"
},
"receiveFromTransferInstrument": {
"enabled": true,
"requested": true,
"allowed": false,
"verificationStatus": "pending"
},
"sendToTransferInstrument": {
"enabled": true,
"requested": true,
"allowed": false,
"verificationStatus": "pending"
},
"sendToBalanceAccount": {
"enabled": true,
"requested": true,
"allowed": false,
"verificationStatus": "pending"
},
"receiveFromBalanceAccount": {
"enabled": true,
"requested": true,
"allowed": false,
"verificationStatus": "pending"
}
},
"id": "AH00000000000000000001",
"status": "active"
}
},
"environment": "test",
"timestamp": "2024-12-15T15:42:03+01:00",
"type": "balancePlatform.accountHolder.created"
}