Provide signed payload on order requests for additional security
Order Execution with Signed Intent
Execute orders with cryptographic signatures to ensure non-repudiation and prevent tampering by intermediate systems.
Overview
Signed intent is an optional security feature that allows you to cryptographically sign critical order parameters using your RSA private key. This is the same feature described in API Key Management → but applied to WebSocket order execution.
Standard order execution is covered on the Order Execution → page. This page only covers the additional signed intent functionality.
When to Use Signed Intent
Consider using signed intent when you require:
| Requirement | Benefit |
|---|---|
| Non-repudiation | Prove the order came from you |
| High-value trades | Additional security for large orders |
| Audit trails | Cryptographic proof for compliance |
| Multi-layer architecture | Prevent middleware tampering |
| Regulatory compliance | Meet institutional security requirements |
Optional FeatureOrders without
signedIntentcontinue to work normally using standard API authentication. Use signed intent only when you need these additional guarantees.
Prerequisites
Before using signed intent: See API Key Management → for detailed setup instructions.
Signed Intent Structure
Add a signedIntent object to your standard order message →:
{
"messageType": "order",
"quoteId": "YdB4DodjOC5FkBDrYmKLW/Dwnz+x9bVbXyshrwz8yntjA+kZ...",
"side": "BUY",
"clientRequestId": "TESTCLIENT1",
"accountGrpUuid": "7687282f-1073-441b-9ff4-694e6b49effe",
"quantity": "100000",
"signedIntent": {
"payload": "eyJxdW90ZUlkIjoiMjAyNDAzMTUtQVREVVNELTk5ODg3NyIs...",
"signature": "M4eIw8Db3jKl9xQ2vN7pZ..."
}
}Signed Intent Fields
| Field | Type | Description |
|---|---|---|
payload | String | Base64-encoded JSON containing order parameters |
signature | String | Base64-encoded RSA signature of the payload |
What to Sign (Payload Contents)
The payload is a JSON object containing only the critical order parameters:
{
"quoteId": "YdB4DodjOC5FkBDrYmKLW/Dwnz+x9bVbXyshrwz8yntjA+kZ...",
"side": "BUY",
"quantity": "100000",
"timestamp": 1710490000000
}Payload Fields
| Field | Required | Type | Description |
|---|---|---|---|
quoteId | Yes | String | Quote ID from price stream |
side | Yes | String | BUY or SELL |
quantity | Yes | String | Trade quantity |
timestamp | Recommended | Long | Unix timestamp in milliseconds (prevents replay attacks) |
Signature Generation Process
Step-by-Step
Step One: Create JSON
- Construct the payload JSON object with required fields
Step Two: Base64 Encode Payload
- Encode the JSON string to Base64
- This becomes your
payloadfield
Step Three: Sign the Base64 String
- Sign using your RSA private key with SHA256
- Use PKCS1v15 padding
Step Four: Base64 Encode Signature
- Encode the binary signature to Base64
- This becomes your
signaturefield
Critical: Sign the Base64 PayloadYou sign the Base64-encoded payload string, not the raw JSON. This is important for signature verification to succeed.
Algorithm Details
- Signing Algorithm: RSA-SHA256 (RS256)
- Padding: PKCS1v15
- Key Size: 4096-bit (recommended) or 2048-bit (minimum)
- Encoding: Base64 for both payload and signature
Implementation Examples
Python Implementation
import json
import base64
import time
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
def generate_signed_order(quote_id, side, quantity, private_key_path):
"""Generate signed intent for order execution"""
# Load Private Key
with open(private_key_path, "rb") as key_file:
private_key = serialization.load_pem_private_key(
key_file.read(),
password=None
)
# Prepare the Payload JSON
intent_json = json.dumps({
"quoteId": quote_id,
"side": side,
"quantity": str(quantity),
"timestamp": int(time.time() * 1000)
})
# Base64 Encode the Payload
payload_b64 = base64.b64encode(
intent_json.encode('utf-8')
).decode('utf-8')
# Sign the Base64 Payload String (not the raw JSON)
signature = private_key.sign(
payload_b64.encode('utf-8'),
padding.PKCS1v15(),
hashes.SHA256()
)
# Base64 Encode the Signature
signature_b64 = base64.b64encode(signature).decode('utf-8')
return {
"payload": payload_b64,
"signature": signature_b64
}
# Usage
signedIntent = generate_signed_order(
quote_id="20240315-AEDUSD-998877",
side="BUY",
quantity=1.5,
private_key_path="zodia_private_key.pem"
)
# Complete order message
order_message = {
"messageType": "order",
"quoteId": "20240315-AEDUSD-998877",
"side": "BUY",
"clientRequestId": "my-ref-001",
"accountGrpUuid": "acc-grp-768",
"quantity": "1.5",
"signedIntent": signedIntent
}
ws.send(json.dumps(order_message))JavaScript Implementation
const crypto = require('crypto');
const fs = require('fs');
function generateSignedOrder(quoteId, side, quantity, privateKeyPath) {
// Load Private Key
const privateKey = fs.readFileSync(privateKeyPath, 'utf8');
// Prepare the Payload JSON
const intentJson = JSON.stringify({
quoteId: quoteId,
side: side,
quantity: quantity.toString(),
timestamp: Date.now()
});
// Base64 Encode the Payload
const payloadB64 = Buffer.from(intentJson, 'utf8').toString('base64');
// Sign the Base64 Payload String (not the raw JSON)
const signer = crypto.createSign('RSA-SHA256');
signer.update(payloadB64);
const signature = signer.sign(privateKey);
// Base64 Encode the Signature
const signatureB64 = signature.toString('base64');
return {
payload: payloadB64,
signature: signatureB64
};
}
// Usage
const signedIntent = generateSignedOrder(
'20240315-AEDUSD-998877',
'BUY',
1.5,
'zodia_private_key.pem'
);
// Complete order message
const orderMessage = {
messageType: 'order',
quoteId: '20240315-AEDUSD-998877',
side: 'BUY',
clientRequestId: 'my-ref-001',
accountGrpUuid: 'acc-grp-768',
quantity: '1.5',
signedIntent: signedIntent
};
ws.send(JSON.stringify(orderMessage));Complete Trading Flow with Signed Intent Example
import json
import time
class SignedOrderExecutor:
def __init__(self, ws, private_key_path):
self.ws = ws
self.private_key_path = private_key_path
self.pending_orders = {}
def on_price_update(self, msg):
"""Handle price stream and execute with signed intent"""
if msg['messageType'] != 'pricestream':
return
# Check trading criteria
offer = float(msg['offer']['price'])
if not self.should_trade(offer):
return
# Generate signed order
quote_id = msg['quoteId']
client_id = f"ORDER-{int(time.time())}"
# Create signed intent
signedIntent = generate_signed_order(
quote_id=quote_id,
side='BUY',
quantity=msg['offer']['quantity'],
private_key_path=self.private_key_path
)
# Send order with signed intent
order = {
'messageType': 'order',
'quoteId': quote_id,
'side': 'BUY',
'clientRequestId': client_id,
'quantity': msg['offer']['quantity'],
'signedIntent': signedIntent
}
self.ws.send(json.dumps(order))
self.pending_orders[client_id] = {
'quote_id': quote_id,
'sent_at': time.time()
}
print(f"Sent signed order: {client_id}")
def on_order_response(self, msg):
"""Handle order confirmation"""
if msg['messageType'] == 'order':
client_id = msg['clientRequestId']
if msg['orderStatus'] == 'SUCCESS':
print(f"Signed order filled: {client_id}")
print(f"Transaction: {msg['transactionId']}")
else:
print(f"Order failed: {msg['message']}")
self.pending_orders.pop(client_id, None)
elif msg['messageType'] == 'error':
print(f"Error {msg['code']}: {msg['message']}")
if msg.get('clientRequestId'):
self.pending_orders.pop(msg['clientRequestId'], None)
def should_trade(self, price):
"""Your trading logic"""
return price < 3.67
# Usage
executor = SignedOrderExecutor(ws, 'zodia_private_key.pem')
def handle_message(ws, message):
msg = json.loads(message)
if msg['messageType'] == 'pricestream':
executor.on_price_update(msg)
elif msg['messageType'] in ['order', 'error']:
executor.on_order_response(msg)class SignedOrderExecutor {
constructor(ws, privateKeyPath) {
this.ws = ws;
this.privateKeyPath = privateKeyPath;
this.pendingOrders = {};
}
onPriceUpdate(msg) {
if (msg.messageType !== 'pricestream') return;
// Check trading criteria
const offer = parseFloat(msg.offer.price);
if (!this.shouldTrade(offer)) return;
// Generate signed order
const quoteId = msg.quoteId;
const clientId = `ORDER-${Date.now()}`;
// Create signed intent
const signedIntent = generateSignedOrder(
quoteId,
'BUY',
msg.offer.quantity,
this.privateKeyPath
);
// Send order with signed intent
const order = {
messageType: 'order',
quoteId: quoteId,
side: 'BUY',
clientRequestId: clientId,
quantity: msg.offer.quantity,
signed_intent: signedIntent
};
this.ws.send(JSON.stringify(order));
this.pendingOrders[clientId] = {
quoteId: quoteId,
sentAt: Date.now()
};
console.log(`Sent signed order: ${clientId}`);
}
onOrderResponse(msg) {
if (msg.messageType === 'order') {
const clientId = msg.clientRequestId;
if (msg.orderStatus === 'SUCCESS') {
console.log(`Signed order filled: ${clientId}`);
console.log(`Transaction: ${msg.transactionId}`);
} else {
console.error(`Order failed: ${msg.message}`);
}
delete this.pendingOrders[clientId];
}
else if (msg.messageType === 'error') {
console.error(`Error ${msg.code}: ${msg.message}`);
if (msg.clientRequestId) {
delete this.pendingOrders[msg.clientRequestId];
}
}
}
shouldTrade(price) {
return price < 3.67;
}
}
// Usage
const executor = new SignedOrderExecutor(ws, 'zodia_private_key.pem');
ws.on('message', (data) => {
const msg = JSON.parse(data);
if (msg.messageType === 'pricestream') {
executor.onPriceUpdate(msg);
} else if (['order', 'error'].includes(msg.messageType)) {
executor.onOrderResponse(msg);
}
});Verification Process
When you submit an order with signedIntent, Zodia Markets receives the order with both standard fields and signed intent and then performs Signature validation using your registered public key. Base64 payload is decoded to get signed parameters and compared with order message fields. If the signature is valid and parameters match, order executes
Verification Failures
{
"timestamp": 1718113605032,
"messageType": "error",
"message": "Signature verification failed",
"code": "INVALID_SIGNATURE",
"clientRequestId": "my-ref-001"
}