Request Authentication and Signing (RSA)
This section describes the authentication of requests to the HighHelp API using the RSA-SHA256 algorithm and the format of the HTTP headers that must be sent in each request.
Asymmetric cryptography is used: the private key is stored in your integration and is used to sign requests, the public key is stored on the HighHelp side and is used to verify the signature.
RSA-SHA256 is the default signature algorithm for all new checkouts. When creating a cash register, a pair of RSA keys is automatically generated.
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.
A description of the alert signature format and verification algorithm is given in the sectionAlert Signing (RSA).
Obtaining API keys
To obtain RSA keys for signing requests:
-
Afterregistration in the systemgo to the tabAPI.
-
Find the desired cash register and click on the gear icon.
-
In the window that opens, click the buttonUpdate API keys.
-
In the windowAPI keyscheck the cash register information.
-
Press and hold the buttonGenerate keyuntil generation is complete.
-
Download and save:
-
private_key_<id>.pem— private RSA key for signing requests; -
public_key_<id>.pem— public RSA key for signing requests; -
hmac_private_key_<id>.txt— HMAC key for signing requests (used with HMAC-SHA512).
-
|
The private RSA key is generated on the browser side and is not saved on the HighHelp side. On the HighHelp side, only the public RSA key of the cash register is stored, which is used to verify the signature of requests. |
Updating API keys
To update the cash register API keys:
-
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.
-
In a modal windowAPI keyscheck the cash register information.
-
Press and hold the buttonGenerate keyuntil generation is complete.
-
Save the generation result:
-
ID— cash register identifier (UUID). Used when interacting with the API and transmitted in the headerx-access-merchant-id; -
private_key_<id>.pem— new private RSA key for signing requests; -
public_key_<id>.pem— new public RSA key for signing requests; -
hmac_private_key_<id>.txt— new HMAC key for signing requests.
-
-
Update your integration configuration by replacing old keys with new ones.
|
Once you update your API keys, the old keys will no longer work immediately. Please ensure that the new keys are configured correctly in your integration before use. It is recommended to perform the update during periods of minimal system load. |
|
ButtonUpdate API keysupdates only request signing keys and does not affect alert signing keys. |
API Authentication
To authenticate API requests and verify the integrity of the request body, use the following HTTP headers:
-
x-access-timestamp -
x-access-merchant-id -
x-access-merchant-algorithm(optional for RSA) -
x-access-signature -
x-access-token
Headingx-access-timestamp
x-access-timestampcontains the time the request was generated.
-
Format — Unix timestamp in seconds (number of seconds since 01/01/1970 00:00:00 UTC).
-
Type — string containing a decimal integer.
Example:
x-access-timestamp: 1716299720
Headingx-access-merchant-id
x-access-merchant-idcontains the cash register identifier.
-
Meaning —
ID(UUID) obtained when generating keys for the cash register. -
Type — string.
In the code examples, the cash register ID is passed through a variableproject_id.
Example:
x-access-merchant-id: 57aff4db-b45d-42bf-bc5f-b7a499a01782
Headingx-access-merchant-algorithm
x-access-merchant-algorithmallows you to explicitly specify the signature algorithm to use.
-
For RSA, the header is optional.
-
Possible value for RSA:
RSA-SHA256(agree on the value with a HighHelp specialist).
Headingx-access-token
x-access-tokencontains the public key of the cash register, encoded in Base64Url format.
The order of formation of the valuex-access-token:
-
Download the private RSA key in PEM format.
-
Get the public key from it.
-
Export the public key in binary form.
-
Encode the received bytes into Base64Url.
-
Pass the resulting string in the header
x-access-token.
Example of value calculationx-access-tokenin Python (librarycryptography):
from cryptography.hazmat.primitives import serialization import base64 # private_key: , PEM public_key_bytes = private_key.public_key().public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, ) api_key = base64.urlsafe_b64encode(public_key_bytes).decode("utf-8") # api_key x-access-token
Headingx-access-signature
x-access-signaturecontains a digital signature of the request body and timestamp.
Signature algorithm (schematically):
message = base64url(normalized_payload) + str(timestamp)
signature = RSA-SHA256(message)
x-access-signature = base64url(signature)
Where:
-
normalized_payload— normalized representation of the JSON body of the request; -
timestamp— header meaningx-access-timestamp(Unix timestamp in seconds).
If there is no request body, use an empty object{}.
Request body normalization
To generate the signature, a normalized representation of the JSON body of the request is used.
Normalization algorithm:
-
Traverse the JSON structure recursively.
-
For each value, form a path in the form
1: 2:…:. -
For arrays, use element indexes:
:0,:1, … -
For boolean values use:
true→1,false→0. -
Sort all lines alphabetically.
-
Connect the lines via
;.
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)
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') -
Sign the byte string
messageprivate RSA key using the RSA-SHA256 algorithm (PKCS#1 v1.5). -
Encode the signature bytes in Base64Url and pass the result in the header
x-access-signature.
Example request with RSA signature (Python3)
import base64 import json import time import requests from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import serialization url = "https://api.hh-processing.com/api/v1/payment/p2p/payin" # (UUID) project_id = "57aff4db-b45d-42bf-bc5f-b7a499a01782" # RSA- (PEM) with open("private_key.pem", "rb") as key_file: private_key = serialization.load_pem_private_key( key_file.read(), password=None, ) 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): result = [] parse_json("", data, result) result.sort() return ";".join(result) # (Base64Url) public_key_bytes = private_key.public_key().public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, ) api_key = base64.urlsafe_b64encode(public_key_bytes).decode("utf-8") # Unix ( ) timestamp = int(time.time()) # joined_result = normalize_message(payload) # message = "{}{}".format( base64.urlsafe_b64encode(joined_result.encode()).decode("utf-8"), str(timestamp), ).encode("utf-8") # RSA-SHA256 signature_bytes = private_key.sign( message, padding.PKCS1v15(), hashes.SHA256() ) signature_b64url = base64.urlsafe_b64encode(signature_bytes).decode("utf-8") # headers = { "content-type": "application/json", "x-access-token": api_key, "x-access-signature": signature_b64url, "x-access-merchant-id": project_id, "x-access-timestamp": str(timestamp), } # (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 private RSA key on the server side.
-
Do not log the private key.
-
Update your keys when changing a cash register or upon request through a HighHelp specialist.
-
Use the public key to verify the signature.
Signature verification form
Use the form below to verify that the RSA-SHA256 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 of the request into the first field.
-
Paste the RSA private key (PEM).
-
Specify a timestamp in Unix timestamp format.
-
Insert signature
x-access-signature, which needs to be checked. -
Click the buttonCheck signature.
The form will display the step-by-step signature generation process and the calculated signature. If the entered signature does not match, use the calculated value as the correct one.
Sample test data
The following test data can be used to verify the signature:
JSON request body:
{"general":{"project_id":"test-project-123"},"payment":{"amount":100000,"currency":"USD"}}
Private RSA key (PEM):
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
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.