Subscribe to Order Channel with Signed Payload

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:

RequirementBenefit
Non-repudiationProve the order came from you
High-value tradesAdditional security for large orders
Audit trailsCryptographic proof for compliance
Multi-layer architecturePrevent middleware tampering
Regulatory complianceMeet institutional security requirements
📘

Optional Feature

Orders without signedIntent continue 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

FieldTypeDescription
payloadStringBase64-encoded JSON containing order parameters
signatureStringBase64-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

FieldRequiredTypeDescription
quoteIdYesStringQuote ID from price stream
sideYesStringBUY or SELL
quantityYesStringTrade quantity
timestampRecommendedLongUnix 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 payload field

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 signature field
⚠️

Critical: Sign the Base64 Payload

You 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"
}