Request Authentication and Signing (HMAC)
This section describes how to authenticate requests to the HighHelp API using the HMAC-SHA512 algorithm and how to generate a digital signature.
Purpose of signature
The request signature is used for:
-
request authentication (confirmation of key ownership);
-
monitoring the integrity of the JSON request body.
Registration in the system
Register through your merchant's personal account. Access to your personal account is provided by a HighHelp specialist.
-
Create a cash register in the tabBox office.
-
Contact the assigned HighHelp specialist to set up the cash register and carry out verification.
Changing the signature algorithm from RSA to HMAC
Switching RSA to HMAC only applies to alert signatures.
To sign requests, you can use an RSA key or an HMAC key from the API key set. The algorithm is selected by the titlex-access-merchant-algorithm.
The procedure for switching and changing the interface is described in the sectionAlert signature (HMAC): algorithm change.
Generating and updating an HMAC key
The HMAC key for signing requests is generated when generating a set of API keys and uploaded to a filehmac_private_key_<id>.txt.
|
The HMAC key is available for download only at the time of generation. The key is uploaded to a file and is not stored on the HighHelp side in clear text. |
HMAC-key generation
To obtain an HMAC key for signing requests:
-
Open your merchant's personal account.
-
Go to the tabAPI.
-
Find the desired cash register and click on the gear icon.
-
In the window that opens, click the buttonUpdate API keys.
-
Press and hold the buttonGenerate keyuntil generation is complete.
-
Download the file
hmac_private_key_<id>.txt. -
Provide the key to the development team and ensure secure storage.
|
Do not store the secret HMAC key in clear text in repositories, logs and monitoring systems. Use specialized secret storage and limit access to it based on the principle of least privilege. |
Updating the HMAC key
The update is performed in the same operation as the generation and reissues the entire set of request signing keys.
|
Once you update your API keys, the old keys will no longer work immediately. |
|
ButtonUpdate API keysupdates only request signing keys and does not affect alert signing keys. |
A description of the keys and alert signing algorithm is given in the sectionAlert signature (HMAC).
API Authentication
For HMAC authentication of requests, use the following HTTP headers:
-
x-access-timestamp -
x-access-merchant-id -
x-access-merchant-algorithm -
x-access-signature -
x-access-token
Signature algorithm (schematically):
x-access-merchant-algorithm = "HMAC-SHA512"
message = base64url(normalized_payload) + str(timestamp)
x-access-signature = base64url(HMAC_SHA512(secret_key, message))
Where:
-
normalized_payload— normalized representation of the JSON body of the request; -
timestamp— header meaningx-access-timestampas a string; -
secret_key— secret HMAC key of the cash register (string).
If there is no request body, use an empty object{}and normalize it as regular JSON.
Headingx-access-timestamp
x-access-timestampcontains the request generation time in Unix timestamp format (number of seconds since 01/01/1970 00:00:00 UTC), specified by the line.
Example:
x-access-timestamp: 1716299720
|
Use server time synchronized via NTP. Do not use the local time of the browser client or mobile application. |
Headingx-access-merchant-id
x-access-merchant-idcontains the cash register identifier. Use valueUUID, obtained when generating keys for the cash register.
In the code examples, the identifier is passed through a variableproject_id.
Example:
x-access-merchant-id: 57aff4db-b45d-42bf-bc5f-b7a499a01782
Headingx-access-merchant-algorithm
x-access-merchant-algorithmdefines the signature algorithm used for this request.
For HMAC use the value:
x-access-merchant-algorithm: HMAC-SHA512
|
For HMAC signature, always specify |
Headingx-access-token
x-access-tokencontains the mask of the secret HMAC key. The mask is used to identify the key without revealing the full secret.
Form the mask according to the following rule:
< 3 > + 7 (*) + < 3 >
An example of a mask function:
def masked_hmac_key(key: str, start_inx: int = 3, end_inx: int = 3) -> str:
if not key or len(key) <= start_inx + end_inx:
return "*******"
return key[:start_inx] + "*******" + key[-end_inx:]
Example header value:
x-access-token: abc*******xyz
|
For HMAC authentication header |
|
Do not pass the full HMAC secret key in the headers, request body, or URL. Only use the mask when |
Headingx-access-signature
x-access-signaturecontains a digital signature of the request. The signature is generated using the HMAC-SHA512 algorithm.
Format (schematic):
message = base64url(normalized_payload) + str(timestamp)
signature_bytes = HMAC_SHA512(secret_key, message)
x-access-signature = base64url(signature_bytes)
The procedure for forming a signature:
-
Normalize the request body to a string
normalized_payloadusing a normalization algorithm. -
Code the term
normalized_payloadin Base64Url, get the stringencoded_payload. -
Concatenate
encoded_payloadAndtimestamp(header valuex-access-timestamp), get the stringmessage. -
Compute HMAC-SHA512 from
messageusing a secret HMAC key. -
Encode the result in Base64Url.
-
Pass the resulting value in the header
x-access-signature.
Request body normalization
To generate the signature, a normalized representation of the JSON body of the request is used.
Normalization algorithm:
-
Perform a recursive traversal of the JSON structure (objects and arrays).
-
Gather pairs in the format
:, where the path is built through a colon:-
for objects:
parent:; -
for arrays:
parent:.
-
-
Convert boolean values:
trueV1,falseV0. -
Use the standard string representation of numbers without localization. Do not add leading zeros.
-
Sort all pairs alphabetically.
-
Concatenate pairs into one line, separating them with the symbol
;.
Sample JSON source:
{
"amount": 100,
"status": "success",
"is_paid": true,
"data": {
"id": 123,
"is_active": false
}
}
Normalization result:
amount:100;data:id:123;data:is_active:0;is_paid:1;status:success
Example implementation of normalization (Python3)
def parse_json(prefix, obj, result): """ JSON- : . """ if isinstance(obj, dict): for key, value in obj.items(): if isinstance(key, bool): key = int(key) new_prefix = f"{prefix}:{key}" if prefix else str(key) parse_json(new_prefix, value, result) elif isinstance(obj, list): for index, item in enumerate(obj): if isinstance(item, bool): item = int(item) new_prefix = f"{prefix}:{index}" parse_json(new_prefix, item, result) else: if isinstance(obj, bool): obj = int(obj) if obj is None: obj = "" result.append(f"{prefix}:{obj}") def normalize_message(payload: dict) -> str: """ JSON ( : : ;). """ items: list[str] = [] parse_json("", payload, items) items.sort() return ";".join(items)
|
If there is no request body, use an empty object |
Normalization requirements
When implementing the normalization algorithm, consider the following requirements:
-
Boolean values:are converted to an integer representation (
true→1,false→0). -
Null values:are converted to an empty string
"". -
Numbers:use standard string representation without localization (thousand separators, local formats). Do not add leading zeros.
-
Arrays:the order of the elements is maintained in the original sequence. Element indices are added to the path as
:0,:1,:2, … -
Objects:after all pairs have been formed
:Sorts alphabetically by complete string. -
Character encoding:use UTF-8 for encoding before using Base64Url. Do not change the case of characters.
-
Base64Url:encode the string in Base64Url format (RFC 4648): replace
+on-,/on_; alignment characters=do not delete. -
Spaces and formatting:do not add or remove spaces in values. Use the exact values from the JSON structure.
Signature generation algorithm
-
Shape Object
payloadwith the request body. -
Normalize
payloadfunctionnormalize_message():joined_result = normalize_message(payload) -
Base64Url encode the normalized string and add a timestamp:
timestamp = int(time.time()) message = "{}{}".format( base64.urlsafe_b64encode(joined_result.encode()).decode("utf-8"), str(timestamp), ).encode("utf-8") -
Compute HMAC-SHA512 from UTF-8 bytes
messageusing a secret HMAC key. -
Encode the result in Base64Url.
-
Pass the resulting value in the header
x-access-signature.
Example request with HMAC signature (Python3)
Below is an example of generating an HMAC-SHA512 signature and sending a request to the API.
import base64 import json import time import hmac import hashlib import requests url = "https://api.hh-processing.com/api/v1/payment/p2p/payin" # (UUID) project_id = "57aff4db-b45d-42bf-bc5f-b7a499a01782" # HMAC- ( ) secret_key = "<YOUR-HMAC-SECRET-KEY>" payload = { "general": { "project_id": project_id } } def parse_json(prefix, obj, result): if isinstance(obj, dict): for key, value in obj.items(): if isinstance(key, bool): key = int(key) new_prefix = f"{prefix}:{key}" if prefix else str(key) parse_json(new_prefix, value, result) elif isinstance(obj, list): for index, item in enumerate(obj): if isinstance(item, bool): item = int(item) new_prefix = f"{prefix}:{index}" parse_json(new_prefix, item, result) else: if isinstance(obj, bool): obj = int(obj) if obj is None: obj = "" result.append(f"{prefix}:{obj}") def normalize_message(data: dict) -> str: items = [] parse_json("", data, items) items.sort() return ";".join(items) def masked_hmac_key(key: str, start_inx: int = 3, end_inx: int = 3) -> str: """ HMAC- . """ if not key or len(key) <= start_inx + end_inx: return "*******" return key[:start_inx] + "*******" + key[-end_inx:] # normalized = normalize_message(payload) # Base64Url encoded = base64.urlsafe_b64encode(normalized.encode("utf-8")).decode("utf-8") # Unix ( ) timestamp = int(time.time()) # message = f"{encoded}{timestamp}".encode("utf-8") # HMAC-SHA512 signature_bytes = hmac.new(secret_key.encode("utf-8"), message, hashlib.sha512).digest() signature_b64url = base64.urlsafe_b64encode(signature_bytes).decode("utf-8") # HMAC- x-access-token hmac_mask = masked_hmac_key(secret_key) # headers = { "content-type": "application/json", "x-access-merchant-id": project_id, "x-access-timestamp": str(timestamp), "x-access-signature": signature_b64url, "x-access-merchant-algorithm": "HMAC-SHA512", "x-access-token": hmac_mask, } # (JSON) dumped = json.dumps(payload, separators=(",", ":")) if payload else "{}" response = requests.post(url, headers=headers, data=dumped) print(response.status_code)
Security Recommendations
-
Store the secret HMAC key on the server side.
-
Update your keys upon request through a HighHelp specialist.
-
Do not transmit keys over unsecured channels.
-
Do not log the key completely. Mask the value: first 3 characters + 7 asterisks (
*) + last 3 characters.Key masking example
def masked_hmac_key(key: str) -> str: if not key or len(key) <= 6: return "*******" return key[:3] + "*******" + key[-3:]
Signature verification form
Use the form below to verify that the HMAC-SHA512 signature is generated correctly for requests to the HighHelp API.
|
Processing of the entered data is performed locally in the browser; data is not transferred to the server. |
Performing a test
-
Paste the JSON body into the first field.
-
Enter the secret key.
-
Specify a timestamp in Unix timestamp format.
-
Insert the signature that needs to be verified.
-
Click the buttonCheck signature.
The form will display the step-by-step process of generating a signature and the result of verifying the provided signature.
Sample test data
The following test data can be used to verify the signature:
JSON-Body:
{"general":{"project_id":"test-project-123"},"payment":{"amount":100000,"currency":"USD"}}
Secret key: test-secret-key-123
Timestamp: 1716299720
Received signature: signature-to-verify
After pressing the buttonCheck signaturethe form will display:
-
Normalized data representation.
-
base64url(normalized). -
message = base64url(normalized) + timestamp. -
Computed signature (
x-access-signature). -
Signature comparison result.