Alert Signing (RSA)
This section describes the algorithm for signing and verifying alerts (callback messages) using RSA keys.
By default, the asymmetric RSA-SHA256 algorithm is used to sign alerts. The symmetric HMAC-SHA512 algorithm is available as an alternative method (see sectionAlert signature (HMAC)).
Purpose of signature
Signing alerts solves the following problems:
-
confirms that the message was sent by the HighHelp platform;
-
ensures that the data in the alert body has not been changed along the way.
|
The public RSA key is displayed in your personal account only with the current RSA-SHA256 algorithm. The current signature algorithm is displayed at the bottom of the windowAPI → Callback settings(lineCurrent algorithm: RSAorCurrent algorithm: HMAC). If the HMAC algorithm is configured for the cash register, use the sectionAlert signature (HMAC). |
Obtaining a public RSA key
To verify the signature of alerts, use the public RSA key for signing alerts configured for the cash register.
How to obtain a key:
-
Open your merchant's personal account.
-
Go to sectionAPI → Callback settings.
-
Make sure that the current algorithm is indicated at the bottom of the window:Current algorithm: RSA.
-
Click on the download icon in the blockPublic Keyand save the key file.
-
Configure the alert processing service to use this key in the signature verification algorithm.
|
With the current RSA algorithm, the window also displays the blockHMAC keyto generate an HMAC key. Generating an HMAC key does not switch the signature algorithm; To change the algorithm, contact the HighHelp manager. |
Alert body normalization
Normalization is performed by recursively traversing the JSON structure and generating a list of strings:followed by sorting.
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 of source data:
{
"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)
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
The signature is generated on the HighHelp side using the following algorithm:
-
Normalization of the JSON body of an alert with a function
normalize_message. -
Encoding a normalized string into Base64Url.
-
Concatenation of the resulting string and
timestamp(line). -
Calculate the SHA-256 hash from the resulting string.
-
Signing a hash with a private RSA key using the RSA-SHA256 scheme.
-
Encoding the signature in Base64Url.
-
Passing signature and timestamp in HTTP notification headers.
In your integration, repeat steps 1-6 and verify the signature using the cash register's public key.
Signature verification on the merchant side
|
To verify the signature, the alert handler performs the following checks:
Example response codes:
|
To verify the signature, follow these steps:
-
Get the JSON body of the alert and header values:
-
x-access-token— public RSA key of the cash register in Base64Url; -
x-access-timestamp— time stamp; -
x-access-signature- signature; -
x-access-merchant-id— cash register identifier for selecting the correct public key.
-
-
Normalize the alert body to a string
normalizedaccording to the described algorithm. -
Encode
normalizedBase64Url. -
Construct the string
message = encoded + timestamp. -
Compute SHA-256 from
message. -
Decode signature from Base64Url.
-
Verify the signature using the public RSA key.
Signature verification example (Python3)
import base64 import binascii from Crypto.Hash import SHA256 from Crypto.PublicKey import RSA from Crypto.Signature.pkcs1_15 import PKCS115_SigScheme def base64url_decode(data: str) -> bytes: normalized = str(data).strip().replace("-", "+").replace("_", "/") if not normalized: raise ValueError("Invalid base64url string") padding = "=" * (-len(normalized) % 4) try: return base64.b64decode(f"{normalized}{padding}", validate=True) except binascii.Error as exc: raise ValueError("Invalid base64url string") from exc def verify_rsa_callback_signature( payload: dict, signature_b64url: str, public_key_pem: bytes, timestamp: int, ) -> bool: """ RSA-SHA256. """ try: # normalized = normalize_message(payload) normalized_bytes = normalized.encode("utf-8") encoded_base64url = base64.urlsafe_b64encode(normalized_bytes).decode("utf-8") concatenated_with_ts = f"{encoded_base64url}{timestamp}" message = concatenated_with_ts.encode("utf-8") # public_key = RSA.import_key(public_key_pem) verifier = PKCS115_SigScheme(public_key) signature = base64url_decode(signature_b64url) # verifier.verify(SHA256.new(message), signature) return True except Exception: return False
Security Recommendations
-
Save the public key of the cash register in the application configuration.
-
Update the public key when the keys in your personal account change.
-
Check idempotency of alerts by fields
project_id,payment_id,status,sub_status.The indempotency check ensures that reprocessing the same alert does not change the final result. Maintain a combination of these fields to prevent duplicate transactions. -
Check the valid time window for
timestamp.
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.