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
Section titled “Authentication”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 and must be increasing for each request. 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. 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.