Alert signature (HMAC)

This section describes the algorithm for signing and verifying alerts (callback messages) using the HMAC key.

The signature uses the symmetric HMAC-SHA512 algorithm and a shared secret key known only to your system and HighHelp.

Purpose of signature

Signing alerts with HMAC solves the following problems:

  • confirms that the message was sent by the HighHelp platform, which owns the secret key;

  • ensures that the data in the alert body has not been changed along the way.

By default, the asymmetric RSA-SHA256 algorithm is used to sign alerts (see sectionAlert Signing (RSA)). HMAC-SHA512 is available as an alternative method.

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.

Before switching, prepare the alert handler to verify the HMAC signature.

To switch the alert signature algorithm, contact your HighHelp manager. The change is carried out on the HighHelp side after approval.

After switching:

  • in the windowAPICallback settingsthe line below is displayedCurrent algorithm: HMAC;

  • only the block is displayed in the windowHMAC key; blockPublic Keynot displayed.

Obtaining an HMAC key

To check the alert signature, use the HMAC key to sign the alerts from the windowAPICallback settings.

The key is uploaded to a filehmac_callback_key_<id>.txtand is available for download only at the time of generation or update.

A separate HMAC key is used to sign requests to the API (see.Request Authentication and Signing (HMAC)).

Generating and updating an HMAC key

When creating cash register keys, an HMAC key and a pair of RSA keys are generated.

If you need to generate an HMAC key for alert signing separately or update an existing key, use the instructions below.

The HMAC key for signing alerts is available for download only at the time of generation or update. After downloading in the sectionAPICallback settingsthe masked key value is displayed; re-download is not available, only key update is available.

For cash registers created before release 2.4.0, the HMAC key for signing alerts may not be available. Use the instructions for generating an HMAC key to sign alerts.

HMAC-key generation

If an HMAC key for signing alerts was not generated for the cash register during initial setup, follow these steps:

  1. Open your merchant's personal account.

  2. Go to sectionAPICallback settings.

  3. Click on the icon+in the blockHMAC key.

  4. Save the key in a secure storage and give it to the development team.

Updating the HMAC key

If you need to update the HMAC key for signing alerts, follow these steps:

  1. Open your merchant's personal account.

  2. Go to sectionAPICallback settings.

  3. Click on the update icon in the blockHMAC key.

  4. In the window that opens, confirm the operation.

  5. Press and hold the confirm button until complete.

  6. Download the filehmac_callback_key_<id>.txt.

  7. Update your integration configuration by replacing the old HMAC alert signing key with a new one.

Updating the HMAC key does not switch the alert signing algorithm.

After updating the HMAC key, the old key will stop working immediately. Make sure the new key is configured correctly in your integration before using it. It is recommended to perform the update during periods of minimal system load.

Alert body normalization

To generate the signature, a normalized representation of the JSON body of the alert is used.

Normalization algorithm:

  1. Traverse the JSON structure recursively.

  2. For each value, form a path in the form1: 2:…​:.

  3. For arrays, use element indexes::0, :1, …​

  4. For boolean values ​​use:true1, false0.

  5. Sort all lines alphabetically.

  6. 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 (true1, false0).

  • 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:

  1. Normalization of the JSON body of an alert with a functionnormalize_message.

  2. Encoding a normalized string into Base64Url.

  3. Concatenation of the resulting string andtimestamp(line).

  4. Calculate HMAC-SHA512 from UTF-8 bytes of the resulting string using the secret key.

  5. Encoding the HMAC value into Base64Url.

  6. Passing signature and timestamp in HTTP notification headers.

In your integration, repeat steps 1-5 and compare the calculated signature with the received one.

Signature verification on the merchant side

To verify the signature, the alert handler performs the following checks:

  • the notification body is present and contains valid JSON;

  • headers are presentx-access-token, x-access-timestamp, x-access-signature;

  • meaningx-access-tokenmatches the mask of the secret HMAC key configured for the cash register;

  • the signature is correct.

Example response codes:

  • 409— empty notification body, invalid JSON, missing required headers, mask mismatchx-access-token, incorrect formatx-access-signature(base64url decode error);

  • 403— signature mismatch;

  • 200— the signature is correct.

To verify a signature:

  1. Get the alert JSON body and header values ​​with:

    • HMAC key mask (x-access-token);

    • timestamp (x-access-timestamp);

    • signature (x-access-signature);

    • cash register identifier (x-access-merchant-id) to select the correct HMAC key.

  2. Find the corresponding secret key for the cash register.

  3. Compare the mask from the headerx-access-tokenwith your HMAC key mask.

  4. Normalize the alert body to a stringnormalizedaccording to the described algorithm.

  5. EncodenormalizedBase64Url.

  6. Construct the stringmessage = encoded + timestamp.

  7. Compute HMAC-SHA512 from UTF-8 bytesmessageusing a secret key.

  8. Decode the signaturex-access-signaturefrom Base64Url to bytes and compare the signature bytes with the calculated value.

Signature verification example (Python3)

import base64 import binascii import hmac import hashlib def masked_hmac_key(key: str) -> str: if not key or len(key) <= 6: return "*******" return key[:3] + "*******" + key[-3:] 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_hmac_callback_signature( payload: dict, signature_b64url: str, access_token: str, secret_key: str, timestamp: str, ) -> bool: """ HMAC-SHA512. """ # (x-access-token) expected_mask = masked_hmac_key(secret_key) if not hmac.compare_digest(expected_mask, access_token): raise ValueError("Invalid x-access-token mask") # normalized = normalize_message(payload) encoded = base64.urlsafe_b64encode(normalized.encode("utf-8")).decode("utf-8") message = f"{encoded}{timestamp}".encode("utf-8") # expected_signature = hmac.new(secret_key.encode("utf-8"), message, hashlib.sha512).digest() received_signature = base64url_decode(signature_b64url) # , return hmac.compare_digest(expected_signature, received_signature)

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:]
  • Check idempotency of alerts by fieldsproject_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 fortimestamp.

Signature verification form

Use the form below to verify that the HMAC-SHA512 signature is generated correctly for HighHelp alerts.

  • To verify alert signatures, use the HMAC key to sign alerts fromAPICallback settings (hmac_callback_key_<id>.txt).

Processing of the entered data is performed locally in the browser; data is not transferred to the server.

Performing a test

  1. Paste the JSON body into the first field.

  2. Enter the secret key.

  3. Specify a timestamp in Unix timestamp format.

  4. Insert the signature that needs to be verified.

  5. 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:

  1. Normalized data representation.

  2. base64url(normalized).

  3. message = base64url(normalized) + timestamp.

  4. Computed signature (x-access-signature).

  5. Signature comparison result.