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
| Header | Description |
|---|---|
X-API-KEY | Your API Key |
X-API-TIMESTAMP | Current Unix timestamp in milliseconds |
X-API-SIGNATURE | HMAC-SHA256 signature (hex) |
X-API-NONCE | Unique 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-TIMESTAMPheader - NONCE: Same value as
X-API-NONCEheader - 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 16→9f8e7d6c5b4a3c2d1e0f9a8b7c6d5e4f
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
| Error | Status | Cause |
|---|---|---|
missing required auth headers | 401 | One or more of the four headers is absent |
invalid timestamp | 401 | Timestamp too old or too far in the future |
nonce already used | 401 | The same nonce was submitted within the TTL window |
invalid signature | 401 | HMAC does not match (wrong secret or signing string) |