Skip to content

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.


  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

All authenticated requests must include these headers:

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

Python
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')

Message Format:

path + '\0' + body_json

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

Python
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
Postman
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


Python
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)

Get Account Information:

Python
account = make_api_request('POST', 'api/3/account', {})

Get Transaction List:

Python
transactions = make_api_request('POST', 'api/3/transaction/list', {
'transactionClass': 'RFSTRADE',
'accountGroupUuid': 'afe6280e-163a-4652-a795-34e963063b06'
})

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...

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);


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.