inNovaAPI
Authentication

Request Signing

HMAC-SHA256 request signing for API authentication

Overview

All authenticated requests must include HMAC-SHA256 signatures. This prevents request tampering and replay attacks.

Required Headers

HeaderDescription
X-API-KEYYour API Key
X-API-TIMESTAMPCurrent Unix timestamp in milliseconds
X-API-SIGNATUREHMAC-SHA256 signature (hex)
X-API-NONCEUnique random string per request

Signing Process

Step 1: Construct the Signing String

METHOD\n
PATH\n
TIMESTAMP\n
NONCE\n
SHA256(BODY)
  • METHOD: HTTP method in uppercase (GET, POST, DELETE)
  • PATH: Full request path including query string (e.g., /api/ext/v1/events?league=nba)
  • TIMESTAMP: Same value as X-API-TIMESTAMP header
  • NONCE: Same value as X-API-NONCE header
  • SHA256(BODY): SHA-256 hash of the request body (hex). Use hash of empty string for GET requests.

Step 2: Compute HMAC-SHA256

signature = HMAC-SHA256(api_secret, signing_string)

Step 3: Send the Request

Include all four headers with the computed signature.

Examples

cURL

API_KEY="po_live_your_api_key"
API_SECRET="your_api_secret"
TIMESTAMP=$(date +%s%3N)
NONCE=$(openssl rand -hex 16)
PATH="/api/ext/v1/events"
BODY=""

BODY_HASH=$(echo -n "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')
SIGNING_STRING="GET\n${PATH}\n${TIMESTAMP}\n${NONCE}\n${BODY_HASH}"
SIGNATURE=$(echo -ne "$SIGNING_STRING" | openssl dgst -sha256 -hmac "$API_SECRET" -hex | awk '{print $NF}')

curl -s "https://api.winnova.pro${PATH}" \
  -H "X-API-KEY: $API_KEY" \
  -H "X-API-TIMESTAMP: $TIMESTAMP" \
  -H "X-API-SIGNATURE: $SIGNATURE" \
  -H "X-API-NONCE: $NONCE"

Python

import hashlib
import hmac
import time
import uuid
import requests

API_KEY = "po_live_your_api_key"
API_SECRET = "your_api_secret"
BASE_URL = "https://api.winnova.pro"

def make_request(method: str, path: str, body: str = ""):
    timestamp = str(int(time.time() * 1000))
    nonce = uuid.uuid4().hex
    body_hash = hashlib.sha256(body.encode()).hexdigest()

    signing_string = f"{method}\n{path}\n{timestamp}\n{nonce}\n{body_hash}"
    signature = hmac.new(
        API_SECRET.encode(), signing_string.encode(), hashlib.sha256
    ).hexdigest()

    headers = {
        "X-API-KEY": API_KEY,
        "X-API-TIMESTAMP": timestamp,
        "X-API-SIGNATURE": signature,
        "X-API-NONCE": nonce,
    }

    resp = requests.request(method, f"{BASE_URL}{path}", headers=headers, data=body)
    return resp.json()

# Example: Get events
events = make_request("GET", "/api/ext/v1/events")
print(events)

JavaScript

async function makeRequest(method, path, body = '') {
  const timestamp = Date.now().toString();
  const nonce = crypto.randomUUID().replace(/-/g, '');

  const bodyHash = Array.from(
    new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(body)))
  ).map(b => b.toString(16).padStart(2, '0')).join('');

  const signingString = `${method}\n${path}\n${timestamp}\n${nonce}\n${bodyHash}`;
  const key = await crypto.subtle.importKey(
    'raw', new TextEncoder().encode(API_SECRET),
    { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
  );
  const sig = Array.from(
    new Uint8Array(await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(signingString)))
  ).map(b => b.toString(16).padStart(2, '0')).join('');

  const resp = await fetch(`https://api.winnova.pro${path}`, {
    method,
    headers: {
      'X-API-KEY': API_KEY,
      'X-API-TIMESTAMP': timestamp,
      'X-API-SIGNATURE': sig,
      'X-API-NONCE': nonce,
      ...(body ? { 'Content-Type': 'application/json' } : {}),
    },
    ...(body ? { body } : {}),
  });
  return resp.json();
}

Nonce Format

The nonce must be a unique random string per request. Recommended formats:

  • UUID v4 (without dashes): crypto.randomUUID().replace(/-/g, '')550e8400e29b41d4a716446655440000
  • Random hex: openssl rand -hex 169f8e7d6c5b4a3c2d1e0f9a8b7c6d5e4f

The same nonce value must never be reused — the server rejects duplicate nonces within a 7-minute window.

Validation Rules

  • Timestamp: Must be within 5 minutes of server time
  • Nonce: Each nonce can only be used once (7-minute TTL)
  • Signature: Constant-time comparison to prevent timing attacks

Error Responses

ErrorStatusCause
missing required auth headers401One or more of the four headers is absent
invalid timestamp401Timestamp too old or too far in the future
nonce already used401The same nonce was submitted within the TTL window
invalid signature401HMAC does not match (wrong secret or signing string)