Subscribe to Order Channel with Signed Payload
Provide signed payload on order requests for additional security
Order Execution with Signed Intent
Section titled “Order Execution with Signed Intent”Execute orders with cryptographic signatures to ensure non-repudiation and prevent tampering by intermediate systems.
Overview
Section titled “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
Section titled “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 |
Prerequisites
Section titled “Prerequisites”Before using signed intent: See API Key Management → for detailed setup instructions.
Signed Intent Structure
Section titled “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
Section titled “Signed Intent Fields”| Field | Type | Required | Description |
|---|---|---|---|
payload |
string | Base64-encoded JSON containing order parameters | |
signature |
string | Base64-encoded RSA signature of the payload |
What to Sign (Payload Contents)
Section titled “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
Section titled “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
Section titled “Signature Generation Process”Step-by-Step
Section titled “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
Algorithm Details
Section titled “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
Section titled “Implementation Examples”Python Implementation
Section titled “Python Implementation”import jsonimport base64import timefrom cryptography.hazmat.primitives import hashes, serializationfrom 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 }
# UsagesignedIntent = generate_signed_order( quote_id="20240315-AEDUSD-998877", side="BUY", quantity=1.5, private_key_path="zodia_private_key.pem")
# Complete order messageorder_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
Section titled “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 };}
// Usageconst signedIntent = generateSignedOrder( '20240315-AEDUSD-998877', 'BUY', 1.5, 'zodia_private_key.pem');
// Complete order messageconst 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
Section titled “Complete Trading Flow with Signed Intent Example”import jsonimport 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
# Usageexecutor = 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; }}
// Usageconst 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
Section titled “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
Section titled “Verification Failures”{ "timestamp": 1718113605032, "messageType": "error", "message": "Signature verification failed", "code": "INVALID_SIGNATURE", "clientRequestId": "my-ref-001"}