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.
How Signature Authentication Works
Section titled “How Signature Authentication Works”- You generate a message string from request components (path + body)
- You sign this message using your API secret and HMAC-SHA512
- You send the signature in request headers (not the secret)
- Zodia Markets verifies the signature using your registered API key
Required Headers
Section titled “Required Headers”All authenticated requests must include these headers:
Rest-Key: <your_api_key>Rest-Sign: <generated_signature>Generating Signatures
Section titled “Generating Signatures”Core Signature Function Example
Section titled “Core Signature Function Example”import hmacimport hashlibimport 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
Section titled “API Signature Generation”Message Format:
path + '\0' + body_jsonThe \0 is a null byte separator between path and body.
import timeimport 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_jsonfunction 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 payloadconst requestBodyObj = { transactionClass: 'RFSTRADE', accountGroupUuid: pm.variables.get('account_group_uuid')};
// Add toncerequestBodyObj.tonce = moment().valueOf() * 1000; // Microsecondsconst requestBodyString = JSON.stringify(requestBodyObj);
// Generate signatureconst 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 requestpm.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: it must be within 2 minutes of server time, and the same value may be used only once. Included in request body, not headers
Making Authenticated Requests
Section titled “Making Authenticated Requests”Complete Request Example
Section titled “Complete Request Example”import requestsimport jsonimport 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 infoaccount = 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 infomakeApiRequest('POST', 'api/3/account') .then(account => console.log(account));Common Request Examples
Section titled “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
Section titled “Postman examples”Setup Environment Variables
Section titled “Setup Environment Variables”Create these variables in your Postman environment:
| Variable | Description | Example |
|---|---|---|
Rest-Key |
Your API key | abc123... |
Rest-Secret |
Your API secret (base64) | ZGVmNDU2... |
account_group_uuid |
Your account group ID | afe6280e-163a-4652... |
Pre-Request Script
Section titled “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 endpointconst path = 'api/3/transaction/list';
// Request payload - customize as neededconst requestBodyObj = { transactionClass: 'RFSTRADE', accountGroupUuid: pm.variables.get('account_group_uuid')};
// Add toncerequestBodyObj.tonce = moment().valueOf() * 1000;const requestBodyString = JSON.stringify(requestBodyObj);
// Generate signatureconst 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 requestpm.variables.set('Rest-Sign', CryptoJS.enc.Base64.stringify(hmac.finalize()));pm.variables.set('postBody', requestBodyString);Frequently Asked Questions
Section titled “Frequently Asked Questions”Q: Why do I need to include a tonce in every request?
A: The tonce (time-once) prevents replay attacks. Because it is a timestamp, it must be within 2 minutes of server time, and the same tonce cannot be used twice for the same API key — so an intercepted request cannot be replayed. Tonces do not have to arrive in increasing order. If you send a nonce instead, the rule is the opposite: a nonce has no time window, but each one must be strictly greater than the last you used.
Q: What happens if my system clock is wrong?
A: If your tonce is more than 2 minutes in the past or future, the request will be rejected. The window is symmetric, so a clock that runs fast fails just as a slow one does. 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: A signature is bound to the tonce it was computed over, so it is accepted only while that tonce is within 2 minutes of server time, and only once — the same tonce cannot be presented twice. Sending a newer tonce does not invalidate an earlier signature that is still inside the window.
