Generate Signature

All API requests must be authenticated using HMAC-SHA512 signatures. This protects against man-in-the-middle attacks by never transmitting your API secret over the network.

Authentication


🔐

Security Warning

Never use production API keys in online code compilers or shared environments. Always generate signatures in a secure, controlled environment.

How Signature Authentication Works

  1. You generate a message string from request components (path + body)
  2. You sign this message using your API secret and HMAC-SHA512
  3. You send the signature in request headers (not the secret)
  4. Zodia Markets verifies the signature using your registered API key

Required Headers

All authenticated requests must include these headers:

Rest-Key: <your_api_key>
Rest-Sign: <generated_signature>

Generating Signatures

Core Signature Function Example

import hmac
import hashlib
import base64

def generate_signature(secret, message):
    """Generate HMAC-SHA512 signature"""
    secret_bytes = base64.b64decode(secret)
    signature = hmac.new(
        secret_bytes,
        message.encode('utf-8'),
        digestmod=hashlib.sha512
    ).digest()
    return base64.b64encode(signature).decode('utf-8')
const crypto = require('crypto');

function generateSignature(secret, message) {
    const secretBytes = Buffer.from(secret, 'base64');
    const signature = crypto
        .createHmac('sha512', secretBytes)
        .update(message)
        .digest('base64');
    return signature;
}

API Signature Generation

Message Format:

path + '\0' + body_json

The \0 is a null byte separator between path and body.

import time
import json

def generate_api_signature(secret, path, body_dict):
    """Generate API signature"""
    # Add tonce to body
    body_dict['tonce'] = int(time.time() * 1000000)  # Microseconds
    body_json = json.dumps(body_dict)
    
    # Build message: path + null byte + body
    message = path + '\0' + body_json
    
    return generate_signature(secret, message), body_json
function generateApiSignature(secret, path, bodyDict) {
    // Add tonce to body
    bodyDict.tonce = Date.now() * 1000;  // Microseconds
    const bodyJson = JSON.stringify(bodyDict);
    
    // Build message: path + null byte + body
    const message = path + '\0' + bodyJson;
    
    return {
        signature: generateSignature(secret, message),
        body: bodyJson
    };
}
const moment = require('moment');
const CryptoJS = require('crypto-js');

const path = 'api/3/transaction/list';

// Request payload
const requestBodyObj = {
    transactionClass: 'RFSTRADE',
    accountGroupUuid: pm.variables.get('account_group_uuid')
};

// Add tonce
requestBodyObj.tonce = moment().valueOf() * 1000;  // Microseconds
const requestBodyString = JSON.stringify(requestBodyObj);

// Generate signature
const secret = CryptoJS.enc.Base64.parse(pm.variables.get('Rest-Secret'));
const hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA512, secret);
hmac.update(path + '\0' + requestBodyString);

// Set Postman variables for use in request
pm.variables.set('Rest-Sign', CryptoJS.enc.Base64.stringify(hmac.finalize()));
pm.variables.set('postBody', requestBodyString);

Tonce is the Timestamp in microseconds (1/1,000,000 second). Prevents replay attacks and must be increasing for each request. Included in request body, not headers


Making Authenticated Requests

Complete Request Example

import requests
import json
import time

BASE_URL = 'https://trade-uk.sandbox.zodiamarkets.com'
API_KEY = '<your_api_key>'
API_SECRET = '<your_api_secret>'

def make_api_request(method, path, body=None):
    """Make authenticated API request"""
    body = body or {}
    
    # Generate signature
    signature, body_json = generate_api_signature(API_SECRET, path, body)
    
    # Build headers
    headers = {
        'Rest-Key': API_KEY,
        'Rest-Sign': signature,
        'Content-Type': 'application/json'
    }
    
    # Make request
    url = BASE_URL + '/' + path
    response = requests.request(method, url, headers=headers, data=body_json)
    
    return response.json()

# Example: Get account info
account = make_api_request('POST', 'api/3/account')
print(account)
const https = require('https');

const BASE_URL = 'trade-uk.sandbox.zodiamarkets.com';
const API_KEY = '<your_api_key>';
const API_SECRET = '<your_api_secret>';

function makeApiRequest(method, path, body = {}) {
    return new Promise((resolve, reject) => {
        // Generate signature
        const { signature, body: bodyJson } = generateApiSignature(
            API_SECRET, 
            path, 
            body
        );
        
        // Build headers
        const headers = {
            'Rest-Key': API_KEY,
            'Rest-Sign': signature,
            'Content-Type': 'application/json',
            'Content-Length': Buffer.byteLength(bodyJson)
        };
        
        // Make request
        const options = {
            hostname: BASE_URL,
            path: '/' + path,
            method: method,
            headers: headers
        };
        
        const req = https.request(options, (res) => {
            let data = '';
            res.on('data', chunk => data += chunk);
            res.on('end', () => resolve(JSON.parse(data)));
        });
        
        req.on('error', reject);
        req.write(bodyJson);
        req.end();
    });
}

// Example: Get account info
makeApiRequest('POST', 'api/3/account')
    .then(account => console.log(account));

Common Request Examples

Get Account Information:

account = make_api_request('POST', 'api/3/account', {})
makeApiRequest('POST', 'api/3/account', {})
    .then(account => console.log(account));

Get Transaction List:

transactions = make_api_request('POST', 'api/3/transaction/list', {
    'transactionClass': 'RFSTRADE',
    'accountGroupUuid': 'afe6280e-163a-4652-a795-34e963063b06'
})
makeApiRequest('POST', 'api/3/transaction/list', {
    transactionClass: 'RFSTRADE',
    accountGroupUuid: 'afe6280e-163a-4652-a795-34e963063b06'
}).then(transactions => console.log(transactions));

Postman examples

Setup Environment Variables

Create these variables in your Postman environment:

VariableDescriptionExample
Rest-KeyYour API keyabc123...
Rest-SecretYour API secret (base64)ZGVmNDU2...
account_group_uuidYour account group IDafe6280e-163a-4652...

Pre-Request Script

Add this to your request's "Pre-request Script" tab:

const moment = require('moment');
const CryptoJS = require('crypto-js');

// Update the path to match your endpoint
const path = 'api/3/transaction/list';

// Request payload - customize as needed
const requestBodyObj = {
    transactionClass: 'RFSTRADE',
    accountGroupUuid: pm.variables.get('account_group_uuid')
};

// Add tonce
requestBodyObj.tonce = moment().valueOf() * 1000;
const requestBodyString = JSON.stringify(requestBodyObj);

// Generate signature
const secret = CryptoJS.enc.Base64.parse(pm.variables.get('Rest-Secret'));
const hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA512, secret);
hmac.update(path + '\0' + requestBodyString);

// Set variables for request
pm.variables.set('Rest-Sign', CryptoJS.enc.Base64.stringify(hmac.finalize()));
pm.variables.set('postBody', requestBodyString);


Frequently Asked Questions

Q: Why do I need to include a tonce in every request?
A: The tonce (time-once) prevents replay attacks. Each tonce must be larger than the previous one, ensuring requests can't be intercepted and reused.

Q: What happens if my system clock is wrong?
A: If your tonce is more than 1 minute in the past or future, the request will be rejected. Ensure your system time is synchronized with NTP servers.

Q: Can I reuse a signature for multiple requests?
A: No. Each signature is valid for only one request. You must generate a new signature (with a new tonce) for every API call.

Q: Why is the null byte (\0) separator required?
A: The null byte separates the path from the body in the message, preventing certain types of signature manipulation attacks.

Q: How long is an API signature valid?
A: Each signature is single-use and expires when the tonce becomes older than 5 minutes or when a newer tonce is used.