Hercle

WebSocket API Errors

This page describes the error handling patterns for the Hercle WebSocket API, which uses SignalR for real-time communication.

Error Response Format

WebSocket errors are communicated through the subject field in hub responses. When an error occurs, the subject will indicate the error type rather than the expected success subject.

Hub Response Structure

All hub responses follow this structure:

{
  "channel": "eventName",
  "topic": "topicName",
  "subject": "topic.subject",
  "type": "message",
  "clientId": "clientId",
  "data": "{...}"
}

Error Subjects

The Hercle WebSocket API uses the following error subjects:

  • preChecksError - Request failed mandatory validation checks

    • Occurs when command arguments are incorrect, missing, or in wrong format
    • Common causes: Invalid data types, missing required fields, out-of-range values
    • Solution: Verify all command arguments match the expected format and types
  • *.error - General operational or account error

    • Indicates issues with the operation execution or account state
    • Details provided in statusError or message field within the data
    • Examples: Insufficient funds, invalid API key, order rejection

Common Error Scenarios

Authentication Errors

Invalid API Key

  • Cause: API key is incorrect, expired, or not provided
  • Subject: Connection will fail to establish or disconnect immediately
  • Solution: Verify your API key from the Account page and ensure it's correctly passed in the accessTokenFactory

Unauthorized Access

  • Cause: API key doesn't have required permissions
  • Subject: *.error
  • Solution: Check your account KYC status and API key permissions

Validation Errors

Missing Required Arguments

  • Cause: Command invoked without all required arguments
  • Subject: preChecksError
  • Data: Error message explaining missing field
  • Solution: Review command documentation and provide all required arguments

Invalid Argument Format

  • Cause: Argument value doesn't match expected type or format
  • Subject: preChecksError
  • Examples:
    • String provided where number expected
    • Invalid date format
    • Malformed pair name
  • Solution: Ensure arguments match the documented types

Out of Range Values

  • Cause: Numeric values exceed allowed limits
  • Subject: preChecksError
  • Examples:
    • Size below minSize or above maxSize
    • Slippage percentage invalid
    • ClientId exceeds 36 characters
  • Solution: Check pair limits using GetPairs command

Order Errors

Insufficient Funds

  • Cause: Account balance too low for trade
  • Subject: execution.order.executed
  • Data: { "status": 2, "statusError": "Insufficient funds" }
  • Solution: Check balances using GetBalances command

Order Rejected

  • Cause: Order couldn't be executed due to market conditions
  • Subject: execution.order.executed
  • Data: { "status": 2, "statusError": "Order rejected" }
  • Solution: Review order parameters and current market conditions

Invalid Price ID

  • Cause: Price update ID expired or invalid
  • Subject: preChecksError or *.error
  • Solution: Subscribe to fresh price updates and use recent IDs

Subscription Errors

Already Subscribed

  • Cause: Attempting to subscribe to a channel already active
  • Subject: preChecksError
  • Solution: Track active subscriptions to avoid duplicates

Invalid Subscription Parameters

  • Cause: Subscription parameters don't match requirements
  • Subject: preChecksError
  • Examples:
    • Invalid pair name
    • Invalid size value
  • Solution: Verify pair exists using GetPairs and size is within limits

Connection Errors

SignalR Connection Issues

Connection Failed

  • Causes:
    • Network connectivity problems
    • Invalid hub URL or path
    • CORS issues (browser environments)
    • Invalid API key
  • Detection: connection.start() promise rejects
  • Solution: Verify base URL and hub path, check network, validate API key

Connection Dropped

  • Causes:
    • Network interruption
    • Server maintenance window
    • Client inactivity timeout
  • Detection: connection.onclose event fires
  • Solution: Implement automatic reconnection with exponential backoff

Reconnection Failures

  • Causes:
    • Persistent network issues
    • Server unavailable
    • Invalid credentials after reconnection
  • Detection: Multiple failed reconnection attempts
  • Solution: Exponential backoff with maximum retry limit

Maintenance Windows

HercleX has scheduled maintenance that may cause disconnections:

  • Regular Maintenance: Every Wednesday, 10:00 AM - 11:00 AM GMT+1
  • Urgent Upgrades: Communicated in advance when possible
  • Impact: SignalR disconnection during upgrade window

Recommended Handling:

  • Implement automatic reconnection policy
  • Store command state to retry after reconnection
  • Subscribe to status updates at status.hercle.financial

Error Handling Best Practices

1. Implement Reconnection Logic

connection.onclose(async (error) => {
  console.log('Disconnected from SignalR. Attempting to reconnect...');

  let retryCount = 0;
  const maxRetries = 10;
  const baseDelay = 1000; // 1 second

  while (retryCount < maxRetries) {
    try {
      // Exponential backoff: 1s, 2s, 4s, 8s, ...
      const delay = baseDelay * Math.pow(2, retryCount);
      await new Promise((resolve) => setTimeout(resolve, delay));

      await connection.start();
      console.log('Reconnected successfully');

      // Re-subscribe to all active channels
      await resubscribeToChannels();

      break;
    } catch (err) {
      retryCount++;
      console.error(`Reconnection attempt ${retryCount} failed:`, err);

      if (retryCount >= maxRetries) {
        console.error('Max reconnection attempts reached');
        // Notify user or trigger alert
      }
    }
  }
});

2. Handle Command Errors

async function sendCommand(commandName, args) {
  try {
    await connection.invoke(commandName, args);
  } catch (err) {
    console.error(`Command ${commandName} failed:`, err);

    // Check if connection is still alive
    if (connection.state !== signalR.HubConnectionState.Connected) {
      console.log('Connection lost, attempting to reconnect...');
      await connection.start();
      // Retry command after reconnection
      await connection.invoke(commandName, args);
    } else {
      // Command failed for other reasons
      throw err;
    }
  }
}

3. Parse Hub Response Errors

connection.on('executionEvents', (message) => {
  const response = JSON.parse(message);

  // Check for error subjects
  if (response.subject === 'preChecksError') {
    const errorData = JSON.parse(response.data);
    console.error('Pre-check validation failed:', errorData.message);
    // Handle validation error (show to user, log, etc.)
    return;
  }

  if (response.subject.endsWith('.error')) {
    const errorData = JSON.parse(response.data);
    console.error(
      'Operation error:',
      errorData.message || errorData.statusError
    );
    // Handle operation error
    return;
  }

  // Process successful response
  const data = JSON.parse(response.data);

  // For orders, check status field
  if (data.status === 2) {
    console.error('Order cancelled/rejected:', data.statusError);
    // Handle order rejection
    return;
  }

  // Success - process data
  console.log('Received data:', data);
});

4. Validate Before Sending

async function placeOtcOrder(clientId, priceId, side, slippage) {
  // Validate arguments before sending
  if (!clientId || clientId.length > 36) {
    throw new Error('Invalid clientId: must be provided and max 36 characters');
  }

  if (!['buy', 'sell'].includes(side)) {
    throw new Error('Invalid side: must be "buy" or "sell"');
  }

  if (typeof slippage !== 'number' || slippage < 0) {
    throw new Error('Invalid slippage: must be a positive number');
  }

  // Send command
  await connection.invoke('PlaceOtcOrder', [clientId, priceId, side, slippage]);
}

5. Monitor Connection State

// Check connection state before sending commands
function ensureConnected() {
  if (connection.state !== signalR.HubConnectionState.Connected) {
    throw new Error('Not connected to SignalR hub');
  }
}

// Track connection state changes
connection.onreconnecting((error) => {
  console.log('Connection lost, reconnecting...', error);
  // Update UI to show reconnecting state
});

connection.onreconnected((connectionId) => {
  console.log('Reconnected with connection ID:', connectionId);
  // Update UI to show connected state
  // Resubscribe to channels
});

Testing Error Scenarios

Sandbox Environment

Use the sandbox environment to test error handling:

  • Base URL: https://publicapi.sandbox.hercle.financial
  • Hub Path: ExecutionLiveServer/v1

Test Cases

Test Invalid Arguments:

// Should trigger preChecksError
await connection.invoke('PlaceOtcOrder', [
  'clientId',
  'invalidPriceId',
  'invalidSide', // Should be 'buy' or 'sell'
  -1, // Invalid slippage
]);

Test Insufficient Funds:

// First, get available pairs
await connection.invoke('GetPairs', ['clientId']);

// Subscribe to large price update
await connection.invoke('SubscribePairPriceChannel', [
  'clientId',
  'BTCUSDT',
  1000, // Very large size
]);

// Try to place order (should fail with insufficient funds)

Test Connection Recovery:

// Manually close connection
await connection.stop();

// Verify reconnection logic triggers
// Check that subscriptions are restored

Test Expired Price ID:

// Get a price update
let priceId;
connection.on('subscriptions', (message) => {
  const data = JSON.parse(JSON.parse(message).data);
  priceId = data.id;
});

await connection.invoke('SubscribePairPriceChannel', [
  'clientId',
  'BTCUSDT',
  0.01,
]);

// Wait for price to expire (typically a few seconds)
await new Promise((resolve) => setTimeout(resolve, 10000));

// Try to use expired price ID
await connection.invoke('PlaceOtcOrder', ['clientId', priceId, 'buy', 0.1]);
// Should receive error

Additional Resources