Webhook authentication Copy section link Copied!

Since your notification server will be publicly accessible, AgoraPay includes an authentication mechanism so you can verify that incoming events have been genuinely created by AgoraPay.

For additional verification:

The public exit IP range is 158.190.51.32/27, which is the same in both the approval and production environments.

HMAC control Copy section link Copied!

Each webhook notification event contains Http headers in addition to the JSON body (Body-Request).

Http-Header (Operation V3)
Copy
Copied
1
2
3
4
5
6
7
header: {
"User-Agent": "AgoraPay/1.0",
"Host": "YourMarketPlace.fr",
"Content-Type": "application/json",
"Content-Length": 533,
"Authorization": "hmac 1.0/08b72fcf-97e8-4a54-866b-dad9ea7f57b7/1722427893459/00934d0f-8993-4be6-96c2-b9c2d76acec5/CBFABE34D6A05EC6072E7E93C5DF206DBFDCCE6E60BACDB83BFACB60FC41B43E"
}
Copy
Copied

We are going to focus on the "Authorization" HTTP header that includes the following fields, separated by "/".

Fields

Description

Version

The current version of the authorisation header is "hmac 1.0"

Nonce

This is a 36-character random data type uuid v4.

Timestamp

A timestamp in seconds since 1/1/1970 UTC (linux time).

Key Id

This is the identifier for the key generated when the notification account was created.

HMAC

Computed HMAC.

HMAC control example Copy section link Copied!

Let's take the following event received from a POST request on the webhook URL "https://YourMarketPlace.fr/webhook", containing the following HTTP header and JSON payload body (Body-Request) :

Http-Header (Operation V3)
Copy
Copied
1
2
3
4
5
6
7
header: {
"User-Agent": "AgoraPay/1.0",
"Host": "YourMarketPlace.fr",
"Content-Type": "application/json",
"Content-Length": 533,
"Authorization": "hmac 1.0/08b72fcf-97e8-4a54-866b-dad9ea7f57b7/1722427893459/00934d0f-8993-4be6-96c2-b9c2d76acec5/CBFABE34D6A05EC6072E7E93C5DF206DBFDCCE6E60BACDB83BFACB60FC41B43E"
}
Copy
Copied
Request Body (Operation V3)
Copy
Copied
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"amount":"5.00",
"authNumber":"462242",
"commissionAmount":"0.05",
"contractRef":"2102019",
"currencyCode":"EUR",
"eventCode":"operation",
"eventStatusLabel":"None",
"exchangeFees":"0.00",
"maskedPAN":"**************91",
"metaData":
{
"metadata":" "
},
"operationDate":"20240731",
"operationSide":"1",
"operationStatus":"R",
"operationType":"1",
"orderRef":"Spadaro-2024-07-31 11:43:34",
"paymentFees":"0.00",
"paymentMethodTypeId":"4",
"relatedMsgStatus":"0",
"schemeFees":"0.00",
"transRefNo":"38125957",
"transactionId":"7562722",
"versionNumber":"3.0"
}
Copy
Copied

The following fields need to be extracted from the authorisation header:

Field name

Value

version

hmac 1.0

nonce

08b72fcf-97e8-4a54-866b-dad9ea7f57b7

timestamp

1722427893459

keyid

00934d0f-8993-4be6-96c2-b9c2d76acec5

hmac

CBFABE34D6A05EC6072E7E93C5DF206DBFDCCE6E60BACDB83BFACB60FC41B43E

The marketplace must perform the following verifications on the extracted information:

  • Check the HMAC control version is the same as the one you implemented
  • Check that the keyid has the same value as your marketplace key ID (given by your implementation manager)
  • The HMAC you compute corresponds to the computed HMAC passed in the authorisation header,

Compute Hmac Copy section link Copied!

To compute the HMAC, you need to:

  • Compute the SHA-256 hash of the "Request-Body" and Uppercase it:
PHP
Copy
Copied
1
$bodySha256 = strtoupper(hash("sha256", $request_body));
Copy
Copied
  • Create the plain to text string:

    $authorizationElements[1] = nonce
    $authorizationElements[2] = timeStamp
PHP
Copy
Copied
1
$plainTextToComputeHmac = 'POST' . ';' . $url . ';' . $bodySha256 . ';' . $authorizationElements[1] . ';' . $authorizationElements[2];
Copy
Copied

POST;https://YourMarketPlace.fr/webhook;F2C72B386EDA769A19224E6EF049CB3DDA756E09E31EE5C16E3F90005BB5C7FA;08b72fcf-97e8-4a54-866b-dad9ea7f57b7;1722427893459

  • Compute the HMAC-SHA-256 hash, using the $plainTextToComputeHmac and your marketplace Hook HMAC Key (given by your implementation manager) and capitalise it.
PHP
Copy
Copied
1
$hmac256 = hash_hmac('sha256', $plainTextToComputeHmac, pack("H*",$Hmac));
Copy
Copied

Full PHP function:

PHP
Copy
Copied
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Function to verify computed Hmac
function computedHmac($authorizationElements , $request_body){

$url ='https://YourMarketPlace.fr/webhook';
$Hmac = 'HMAC KEY given by you implementation manager';

//compute the HMAC
$bodySha256 = strtoupper(hash("sha256", $request_body));
$plainTextToComputeHmac = 'POST' . ';' . $url . ';' . $bodySha256 . ';' . $authorizationElements[1] . ';' . $authorizationElements[2];
$hmac256 = hash_hmac('sha256', $plainTextToComputeHmac, pack("H*",$Hmac));


$computedHmacOnHeader = $authorizationElements[4];
//compare hmacs
if (strtoupper($computedHmacOnHeader) === strtoupper($hmac256)) {
return true;
}else{
return false;
}
}
Copy
Copied

Other HMAC calculation implementations Copy section link Copied!

Here are some other code examples of the HMAC control algorithm:

Python
Node.js
Copy
Copied
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import hashlib
import hmac
import json


def verify_hmac(
headers: str,
header_auth_version: str,
server_url: str,
body: bytes,
method: str,
path: str,
key_id: str,
hmac_key: str,
) -> bool:
"""
Method verifying the HMAC
:param headers: request headers
:param header_auth_version: header authentication version
:param server_url: Actual URL of the server
:param body: request payload
:param method: request method (GET, POST...)
:param path: request path
:param key_id: specific key id of the hook
:param hmac_key: specific hmac key of the hook
:return: Success (or not) of auth
"""

authorization = headers["authorization"] if "authorization" in headers else None
if not authorization:
return False

auth = authorization.split(" ")
if auth[0] != "hmac":
return False

fields = auth[1].split("/")
if len(fields) != 5:
return False

if fields[0] != header_auth_version:
return False

if fields[3] != key_id:
return False

sha256 = hashlib.sha256(body).hexdigest().upper()
hmac_data = (
method
+ ";"
+ server_url
+ path
+ ";"
+ sha256
+ ";"
+ fields[1]
+ ";"
+ fields[2]
)
hmac_hash = (
hmac.new(str.encode(hmac_key), str.encode(hmac_data), hashlib.sha256)
.hexdigest()
.upper()
)

if not hmac.compare_digest(hmac_hash, fields[4]):
return False

return True
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
const crypto = require("crypto");

function verify_hmac(
headers,
headerAuthVersion,
serverUrl,
body,
method,
path,
keyId,
hmacKey
) {
const authorization = headers["authorization"];

if (!authorization) {
return false;
}

const auth = authorization.split(" ");
if (auth[0] !== "hmac") {
return false;
}

const fields = auth[1].split("/");

if (fields.length != 5) {
return False;
}

if (fields[0] != headerAuthVersion) {
return False;
}

if (fields[3] != keyId) {
return False;
}

const sha256Value = crypto
.createHash("sha256")
.update(JSON.stringify(body))
.digest("hex")
.toUpperCase();


const hmacData =
method +
";" +
serverUrl +
path +
";" +
sha256_value +
";" +
fields[1] +
";" +
fields[2];

const hmac = crypto
.createHmac("sha256", Buffer.from(hmacKey, "hex"))
.update(hmacData)
.digest("hex")
.toUpperCase();

return crypto.timingSafeEqual(hmac, fields[4]);
}
Copy
Copied