Skip to content

Commit 1163e9c

Browse files
authored
Merge pull request #754 from Adyen/hmac-calculate-script
HMAC Troubleshooting tool
2 parents b4cd55e + 160a0d3 commit 1163e9c

File tree

6 files changed

+273
-9
lines changed

6 files changed

+273
-9
lines changed

src/Adyen/Util/HmacSignature.php

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,13 @@ public function validateHMAC(string $hmacKey, string $hmacSign, string $webhook)
3131
}
3232

3333
/**
34+
* Validate the HMAC Signature of the webhook payload
35+
* Used for BankingWebhooks and ManagementWebhooks (where HMAC signature is provided in the HTTP header)
36+
* Note: HMAC signature is calculated considering the entire payload
37+
*
3438
* @param string $hmacKey Can be found in Customer Area
3539
* @param string $hmacSign Can be found in the Webhook headers
36-
* @param string $webhook The response from Adyen
40+
* @param string $webhook The webhook payload
3741
* @return bool
3842
* @throws AdyenException
3943
*/
@@ -42,17 +46,13 @@ public function validateHMACSignature(string $hmacKey, string $hmacSign, string
4246
if (!ctype_xdigit($hmacKey)) {
4347
throw new AdyenException("Invalid HMAC key: $hmacKey");
4448
}
45-
$expectedSign = base64_encode(hash_hmac(
46-
'sha256',
47-
$webhook,
48-
pack("H*", $hmacKey),
49-
true
50-
));
49+
$expectedSign = self::calculateHmacSignature($hmacKey, $webhook);
5150
return hash_equals($expectedSign, $hmacSign);
5251
}
5352
/**
53+
* Calculate HMAC Signature for Payments webhooks
5454
* @param string $hmacKey Can be found in Customer Area
55-
* @param array $params The response from Adyen
55+
* @param array $params NotificationRequestItem object inside the webhook payload
5656
* @return string
5757
* @throws AdyenException
5858
*/
@@ -76,7 +76,7 @@ public function calculateNotificationHMAC($hmacKey, $params)
7676

7777

7878
// base64-encode the binary result of the HMAC computation
79-
$merchantSig = base64_encode(hash_hmac('sha256', $dataToSign, pack("H*", $hmacKey), true));
79+
$merchantSig = self::calculateHmacSignature($hmacKey, $dataToSign);
8080
return $merchantSig;
8181
}
8282

@@ -111,6 +111,10 @@ private function getNotificationDataToSign($params)
111111
}
112112

113113
/**
114+
* Validate the HMAC Signature of the requestItem object included in the webhook payload
115+
* Used for Payment Webhooks (where HMAC signature is provided in the `additionalData` field)
116+
* Note: HMAC signature is calculated considering a subset of the fields (see `calculateNotificationHMAC` function)
117+
*
114118
* @param string $hmacKey
115119
* @param array $params
116120
* @return bool
@@ -180,4 +184,25 @@ public function isHmacSupportedEventCode($response)
180184
);
181185
return array_key_exists(self::EVENT_CODE, $response) && in_array($response[self::EVENT_CODE], $eventCodes);
182186
}
187+
188+
/**
189+
* Calculate HMAC signature
190+
*
191+
* @param string $hmacKey Can be found in Customer Area
192+
* @param string $payload The response from Adyen
193+
* @return string
194+
* @throws AdyenException
195+
*/
196+
public function calculateHmacSignature(string $hmacKey, string $payload): string
197+
{
198+
if (empty($hmacKey)) {
199+
throw new AdyenException("You did not provide a HMAC key");
200+
}
201+
202+
if (!ctype_xdigit($hmacKey)) {
203+
throw new AdyenException("Invalid HMAC key: $hmacKey");
204+
}
205+
206+
return base64_encode(hash_hmac('sha256', $payload, pack("H*", $hmacKey), true));
207+
}
183208
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
// script to calculate the HMAC signature of Banking/Management webhooks (where the signature is calculated over the
4+
// entire webhook payload)
5+
//
6+
// Run with: `php tools/hmac/HMACValidatorBanking.php {hmacKey} {path to JSON file}
7+
// php tools/hmac/HMACValidatorBanking.php 11223344D785FBAE710E7F943F307971BB61B21281C98C9129B3D4018A57B2EB tools/hmac/payload2.json
8+
9+
require_once __DIR__ . '/../../vendor/autoload.php';
10+
11+
use Adyen\Util\HmacSignature;
12+
13+
if ($argc !== 3) {
14+
echo "‼️Error running the script\n";
15+
echo "Usage: php HMACValidatorBanking.php <hmacKey> <payloadFile>\n";
16+
exit(1);
17+
}
18+
19+
20+
$hmacKey = $argv[1];
21+
$payloadFile = $argv[2];
22+
23+
if (!file_exists($payloadFile)) {
24+
echo "Error: File '$payloadFile' not found.\n";
25+
exit(1);
26+
}
27+
28+
// load payload
29+
$payload = file_get_contents($payloadFile);
30+
31+
echo "Calculating HMAC signature with payload from '$payloadFile'\n";
32+
echo "********\n";
33+
echo "Payload file: '$payloadFile'\n";
34+
echo "Payload length: " . strlen($payload) . "\n";
35+
36+
$hmacSignature = new HmacSignature();
37+
$signature = $hmacSignature->calculateHmacSignature($hmacKey, $payload);
38+
39+
echo "HMAC Signature-> $signature\n";
40+
exit(0);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
// script to calculate the HMAC signature of Payments webhooks (where the signature is calculated considering
4+
// a subset of the fields in the payload - i.e. NotificationRequestItem object)
5+
//
6+
// Run with: `php tools/hmac/HMACValidatorPayments.php {hmacKey} {path to JSON file}
7+
// php tools/hmac/HMACValidatorPayments.php 11223344D785FBAE710E7F943F307971BB61B21281C98C9129B3D4018A57B2EB tools/hmac/payload.json
8+
9+
require_once __DIR__ . '/../../vendor/autoload.php';
10+
11+
use Adyen\Util\HmacSignature;
12+
13+
if ($argc !== 3) {
14+
echo "‼️Error running the script\n";
15+
echo "Usage: php HMACValidatorPayments.php <hmacKey> <payloadFile>\n";
16+
exit(1);
17+
}
18+
19+
$hmacKey = $argv[1];
20+
$payloadFile = $argv[2];
21+
22+
if (!file_exists($payloadFile)) {
23+
echo "Error: File '$payloadFile' not found.\n";
24+
exit(1);
25+
}
26+
27+
// load payload as JSON
28+
$payload = file_get_contents($payloadFile);
29+
$jsonData = json_decode($payload, true);
30+
31+
if (json_last_error() !== JSON_ERROR_NONE) {
32+
echo "Error: Invalid JSON in payload file.\n";
33+
exit(1);
34+
}
35+
36+
if (!isset($jsonData['notificationItems']) || !is_array($jsonData['notificationItems'])) {
37+
echo "Error: 'notificationItems' key is missing or not an array.\n";
38+
exit(1);
39+
}
40+
41+
// Fetch the first (and only) NotificationRequestItem
42+
$notificationRequestItem = $jsonData['notificationItems'][0]['NotificationRequestItem'] ?? null;
43+
44+
if ($notificationRequestItem === null) {
45+
echo "Error: 'NotificationRequestItem' is not found.\n";
46+
exit(1);
47+
}
48+
49+
echo "Calculating HMAC signature with payload from '$payloadFile'\n";
50+
echo "********\n";
51+
echo "Payload file: '$payloadFile'\n";
52+
echo "Payload length: " . strlen($payload) . "\n";
53+
54+
55+
// Log notificationRequestItem
56+
//print_r($notificationRequestItem);
57+
58+
$hmacSignature = new HmacSignature();
59+
$signature = $hmacSignature->calculateNotificationHMAC($hmacKey, $notificationRequestItem);
60+
61+
echo "HMAC Signature-> $signature\n";
62+
exit(0);

tools/hmac/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
## HMAC Tools
2+
3+
This folder contains PHP scripts to calculate the HMAC signature of the webhook payload.
4+
They can be used to troubleshoot the HMAC signature calculation.
5+
6+
Note: make sure you are using the HMAC key used to generate the signature associated with the payload in the JSON file
7+
8+
### Payments webhooks
9+
10+
Copy the content of the webhook in the payload.json (or provide a different file), then run with:
11+
`php tools/hmac/HMACValidatorPayments.php {hmacKey} {path to JSON file}`
12+
```
13+
php tools/hmac/HMACValidatorPayments.php 11223344D785FBAE710E7F943F307971BB61B21281C98C9129B3D4018A57B2EB tools/hmac/payload.json
14+
```
15+
16+
### Banking webhooks
17+
18+
Copy the content of the webhook in the payload2.json (or provide a different file), then run with:
19+
`php tools/hmac/HMACValidatorBanking.php {hmacKey} {path to JSON file}`
20+
```
21+
php tools/hmac/HMACValidatorBanking.php 11223344D785FBAE710E7F943F307971BB61B21281C98C9129B3D4018A57B2EB tools/hmac/payload2.json
22+
```

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": "qHNSvxCCix79xgBdWemxf0rcNMWoLrMK/4IgMnkOsWI=",
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": "test@adyen.com",
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)