Authentication

Every request to the Light Horse API must include both an API key and a cryptographic signature. The signature is computed from the full request content — method, path, query string, headers, and body — so any tampering in transit will cause the signature check to fail and the request to be rejected.

Required Headers

Every request must include the following HTTP headers:

x-trade-apikey A unique ID issued when your API key pair is created. This identifies the key to use for cryptographic signature.

x-trade-algorithm The algorithm used to generate the signature. Currently only HMAC-SHA256 is supported.

x-trade-nonce Any arbitrary unique value, for example an UUID. The server rejects any request that reuses a nonce it has already seen. Example: d3a6c7b1-8e4f-4a2d-9c3b-1f8e7d6c5b4a

x-trade-timestamp Unix timestamp (seconds) at the time of the request. Requests with a timestamp older than 5 minutes are rejected. Example: 1765148421, which corresponds to 2025-12-07 23:00:21

x-trade-signature The computed signature (see below).

Computing the Signature

Step 1 — Prepare the request body

  • Serialize the body as json, if the request has no body, use empty JSON object string {}.
  • Compute the MD5 hash of the resulting string.

Step 2 — Build the string to sign

Concatenate the following fields in order, separated by \n:

METHOD\n
REQUEST_PATH\n
QUERY_STRING\n
x-trade-apikey:X-Trade-APIKey\n
x-trade-timestamp:X-Trade-Timestamp\n
x-trade-nonce:X-Trade-Nonce\n
MD5(body)

For example, a POST to /request/url?param1=value1&param2=value2 with empty body produces:

POST\n
/request/url\n
param1=value1&param2=value2\n
x-trade-apikey:739c38fa-0135-494d-88e1-f51e0ecc579c\n
x-trade-timestamp:1705148421\n
x-trade-nonce:d3a6c7b1-8e4f-4a2d-9c3b-1f8e7d6c5b4a\n
99914b932bd37a50b983c5e7c90ae93b

Step 3 — Sign and encode

Compute HMAC-SHA256 over the string to sign using your API secret, then Base64-encode the result:

const bodyHash = crypto
  .createHash('md5')
  .update(JSON.stringify(sortedBody))
  .digest('hex');

const stringToSign = [
  method.toUpperCase(),
  requestPath,
  queryString,
  apiKey,
  timestamp,
  nonce,
  bodyHash,
].join('\n');

let signature = crypto
  .createHmac('sha256', apiSecret)
  .update(stringToSign)
  .digest('hex');

signature = Buffer.from(signature).toString('base64');

Set the resulting value as the x-trade-signature header.

Complete Example

import crypto from 'crypto';

function getSignedHeaders({ method, path, query, body, apiKey, apiSecret }) {
  const nonce = crypto.randomUUID();
  const timestamp = Math.floor(Date.now() / 1000).toString();

  const sortedBody = body || '{}';
  const bodyHash = crypto.createHash('md5').update(sortedBody).digest('hex');

  const stringToSign = [
    method.toUpperCase(),
    path,
    query ?? '',
    `x-trade-apikey:${apiKey}`,
    `x-trade-timestamp:${timestamp}`,
    `x-trade-nonce:${nonce}`,
    bodyHash
  ].join('\n');

  let signature = crypto.createHmac('sha256', apiSecret).update(stringToSign).digest('hex');
  signature = Buffer.from(signature).toString('base64');

  return {
    'x-trade-apikey': apiKey,
    'x-trade-algorithm': 'HMAC-SHA256',
    'x-trade-nonce': nonce,
    'x-trade-timestamp': timestamp,
    'x-trade-signature': signature,
  };
}