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.

  1. Create a cash register in the tabBox office.

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

  1. Open your merchant's personal account.

  2. Go to the tabAPI.

  3. Find the desired cash register and click on the gear icon.

  4. In the window that opens, click the buttonUpdate API keys.

  5. Press and hold the buttonGenerate keyuntil generation is complete.

  6. Download the filehmac_private_key_<id>.txt.

  7. 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 specifyx-access-merchant-algorithm: HMAC-SHA512. If the header is missing or the value is incorrect, the request will be rejected.

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 headerx-access-tokenrequired. If there is no header, the request will be rejected.

Do not pass the full HMAC secret key in the headers, request body, or URL. Only use the mask whenx-access-token.

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:

  1. Normalize the request body to a stringnormalized_payloadusing a normalization algorithm.

  2. Code the termnormalized_payloadin Base64Url, get the stringencoded_payload.

  3. Concatenateencoded_payloadAndtimestamp(header valuex-access-timestamp), get the stringmessage.

  4. Compute HMAC-SHA512 frommessageusing a secret HMAC key.

  5. Encode the result in Base64Url.

  6. Pass the resulting value in the headerx-access-signature.

Request body normalization

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

Normalization algorithm:

  1. Perform a recursive traversal of the JSON structure (objects and arrays).

  2. Gather pairs in the format:, where the path is built through a colon:

    • for objects:parent:;

    • for arrays:parent:.

  3. Convert boolean values:trueV1, falseV0.

  4. Use the standard string representation of numbers without localization. Do not add leading zeros.

  5. Sort all pairs alphabetically.

  6. 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{}(For example,payload = {}) and normalize it as regular JSON.

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

  1. Shape Objectpayloadwith the request body.

  2. Normalizepayloadfunctionnormalize_message():

    joined_result = normalize_message(payload)
  3. 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")
  4. Compute HMAC-SHA512 from UTF-8 bytesmessageusing a secret HMAC key.

  5. Encode the result in Base64Url.

  6. Pass the resulting value in the headerx-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

  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.