Hercle

WebSocket API - Authentication

Hercle's WebSocket API uses SignalR technology for real-time communication. SignalR provides a robust, scalable solution for bidirectional communication between clients and servers.

What is SignalR?

SignalR is a library that simplifies adding real-time web functionality to applications. It uses WebSockets when available and falls back to other techniques when needed. Learn more at Microsoft's SignalR documentation.

Authentication Flow

  1. Create SignalR hub connection with API key
  2. Start the connection
  3. Listen for events
  4. Send commands to the hub

Getting Your API Key

Before you can use the WebSocket API, you must complete the onboarding process and have your KYC approved:

  1. Sandbox: Log in at https://auth.sandbox.hercle.financial/sign-in
  2. Production: Use the production authentication portal
  3. Navigate to the Account page
  4. Go to the API Key area
  5. Either generate a new key or retrieve your existing one

Warning: Never expose your API keys in client-side code or commit them to version control. Store them securely.

Connection URLs

Sandbox Environment

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

Production Environment

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

SignalR Client Libraries

Most major programming languages have SignalR client SDKs available:

JavaScript/TypeScript

NPM: @microsoft/signalr

npm install @microsoft/signalr

Or from CDN:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>

C#

Install-Package Microsoft.AspNetCore.SignalR.Client

Java

Maven:

<dependency>
    <groupId>com.microsoft.signalr</groupId>
    <artifactId>signalr</artifactId>
    <version>7.0.0</version>
</dependency>

Gradle:

implementation 'com.microsoft.signalr:signalr:7.0.0'

Python

pip install signalrcore

C++

SignalR C++ Client on GitHub

Complete Example

JavaScript (Browser)

// Import SignalR from CDN or npm package
// <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>
// or: import * as signalR from '@microsoft/signalr';

const apiKey = process.env.HERCLE_API_KEY; // Use environment variable
const baseUrl = 'https://publicapi.sandbox.hercle.financial';
const hubPath = 'ExecutionLiveServer/v1';

// 1. Create hub connection with API key
const connection = new signalR.HubConnectionBuilder()
  .withUrl(`${baseUrl}/${hubPath}`, {
    accessTokenFactory: () => apiKey,
  })
  .build();

// 2. Listen for events
connection.on('executionEvents', (message) => {
  console.log('Event received:', message);

  // Parse the event data
  const data = JSON.parse(message.data);
  console.log('Event data:', data);
});

// 3. Start the connection
connection
  .start()
  .then(() => {
    console.log('✓ Connected to SignalR hub');

    // 4. Send commands after connection is established
    const clientId = 'client-123';
    connection
      .invoke('GetPairs', [clientId])
      .then(() => console.log('GetPairs command sent'))
      .catch((err) => console.error('Command error:', err));
  })
  .catch((err) => {
    console.error('✗ Connection failed:', err);
  });

// Handle connection close
connection.onclose((error) => {
  console.log('Connection closed', error);
});

Node.js

const signalR = require('@microsoft/signalr');

const apiKey = process.env.HERCLE_API_KEY;
const baseUrl = 'https://publicapi.sandbox.hercle.financial';
const hubPath = 'ExecutionLiveServer/v1';

// Create hub connection
const connection = new signalR.HubConnectionBuilder()
  .withUrl(`${baseUrl}/${hubPath}`, {
    accessTokenFactory: () => apiKey,
  })
  .build();

// Listen for events
connection.on('executionEvents', (message) => {
  console.log('Event received:', message);
  const data = JSON.parse(message.data);
  console.log('Subject:', message.subject);
  console.log('Data:', data);
});

// Start connection
connection
  .start()
  .then(() => {
    console.log('✓ Connected to SignalR hub');

    // Send GetBalances command
    const clientId = 'my-client-id';
    return connection.invoke('GetBalances', [clientId]);
  })
  .then(() => {
    console.log('GetBalances command sent');
  })
  .catch((err) => {
    console.error('Error:', err);
  });

// Handle disconnection
connection.onclose((error) => {
  console.log('Disconnected:', error);
  // Implement reconnection logic here
});

Python

from signalrcore.hub_connection_builder import HubConnectionBuilder
import os
import json

api_key = os.environ.get('HERCLE_API_KEY')
base_url = 'https://publicapi.sandbox.hercle.financial'
hub_path = 'ExecutionLiveServer/v1'

# Create hub connection
hub_connection = HubConnectionBuilder()\
    .with_url(f"{base_url}/{hub_path}",
        options={"access_token_factory": lambda: api_key})\
    .build()

# Handle events
def handle_execution_events(message):
    print("Event received:", message)
    # message is a list with the hub response
    for msg in message:
        data = json.loads(msg['data'])
        print(f"Subject: {msg['subject']}")
        print(f"Data: {data}")

# Register event handler
hub_connection.on('executionEvents', handle_execution_events)

# Start connection
hub_connection.start()
print("✓ Connected to SignalR hub")

# Send command
client_id = 'my-client-id'
hub_connection.invoke('GetPairs', [client_id])
print("GetPairs command sent")

# Keep connection alive
input("Press Enter to disconnect...")
hub_connection.stop()

C#

using Microsoft.AspNetCore.SignalR.Client;
using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        var apiKey = Environment.GetEnvironmentVariable("HERCLE_API_KEY");
        var baseUrl = "https://publicapi.sandbox.hercle.financial";
        var hubPath = "ExecutionLiveServer/v1";

        // Create hub connection
        var connection = new HubConnectionBuilder()
            .WithUrl($"{baseUrl}/{hubPath}", options =>
            {
                options.AccessTokenProvider = () => Task.FromResult(apiKey);
            })
            .Build();

        // Listen for events
        connection.On<dynamic>("executionEvents", (message) =>
        {
            Console.WriteLine($"Event received: {message}");
        });

        // Start connection
        await connection.StartAsync();
        Console.WriteLine("✓ Connected to SignalR hub");

        // Send command
        var clientId = "my-client-id";
        await connection.InvokeAsync("GetBalances", clientId);
        Console.WriteLine("GetBalances command sent");

        Console.ReadLine();
        await connection.StopAsync();
    }
}

Hub Response Format

Events from the hub follow this structure:

{
  channel: 'executionEvents',      // Event channel name
  topic: 'Account',                 // Event topic (e.g., Account, Execution)
  subject: 'account.balances.snapshot',  // Specific event subject
  type: 'message',                  // Message type
  clientId: 'client-123',          // Your client ID
  data: '{...}'                     // JSON string with event data
}

Example Response

{
  channel: 'executionEvents',
  topic: 'Account',
  subject: 'account.balances.snapshot',
  type: 'message',
  clientId: 'my-client-id',
  data: '{"userId":"xxxx","sequence":123,"assets":[{"name":"BTC","available":0.5,"allocated":0.0}],"deleted":false}'
}

Parsing Event Data

connection.on('executionEvents', (message) => {
  // The data field is a JSON string, parse it
  const eventData = JSON.parse(message.data);

  console.log('Subject:', message.subject);
  console.log('Parsed data:', eventData);

  // Handle different event types based on subject
  if (message.subject === 'account.balances.snapshot') {
    console.log('Balances:', eventData.assets);
  } else if (message.subject === 'execution.pairs') {
    console.log('Available pairs:', eventData);
  }
});

Environments

Sandbox Environment

URL: https://publicapi.sandbox.hercle.financial/ExecutionLiveServer/v1

  • ✓ Safe testing environment
  • ✓ No real funds at risk
  • ✓ Test your integration
  • ✓ Same API as production

Use for:

  • Development and testing
  • Learning the API
  • Integration testing
  • CI/CD pipelines

Production Environment

URL: https://publicapi.hercle.financial/ExecutionLiveServer/v1

  • ✓ Real trading environment
  • ✓ Live market data
  • ✓ Execute actual trades
  • ⚠️ Real funds at risk

Use for:

  • Production applications
  • Live trading
  • Real customer accounts

Authentication Errors

Connection Failed

If the connection fails to start, you'll see an error:

connection.start().catch((err) => {
  console.error('Connection failed:', err);
  // Check error details
  console.error('Error message:', err.message);
});

Common causes:

  • Invalid or missing API key
  • API key revoked or expired
  • Network connectivity issues
  • Incorrect hub URL or path
  • KYC not approved

How to fix:

  • Verify your API key is correct and active
  • Check you're using the correct base URL and hub path
  • Ensure your KYC is approved
  • Test with a simple cURL request to verify API access

Insufficient Permissions

If your account doesn't have the required permissions:

Causes:

  • KYC verification not complete
  • Account restrictions
  • API key doesn't have WebSocket access

How to fix:

  • Complete KYC verification
  • Contact support to verify account status
  • Check your API key permissions

Connection Lifecycle

1. Create Connection

const connection = new signalR.HubConnectionBuilder()
  .withUrl(`${baseUrl}/${hubPath}`, {
    accessTokenFactory: () => apiKey,
  })
  .build();

2. Register Event Handlers

// Listen for specific events
connection.on('executionEvents', (message) => {
  console.log('Received:', message);
});

// Handle disconnection
connection.onclose((error) => {
  console.log('Disconnected:', error);
  // Implement reconnection logic
});

3. Start Connection

connection
  .start()
  .then(() => console.log('Connected'))
  .catch((err) => console.error('Failed to connect:', err));

4. Send Commands

// Invoke hub methods after connection is started
connection
  .invoke('GetBalances', [clientId])
  .then(() => console.log('Command sent'))
  .catch((err) => console.error('Command failed:', err));

5. Stop Connection (when done)

connection
  .stop()
  .then(() => console.log('Disconnected'))
  .catch((err) => console.error('Error stopping:', err));

Security Best Practices

1. Never Expose Keys Client-Side

Don't do this:

// Never hardcode in browser JavaScript
const ws = new WebSocket('wss://api.hercle.com/ws');
ws.send(
  JSON.stringify({
    method: 'authenticate',
    params: { apiKey: 'hx_live_1234567890abcdef' }, // EXPOSED!
  })
);

Do this instead:

// Use backend proxy
const ws = new WebSocket('wss://your-backend.com/ws-proxy');
// Your backend handles authentication

2. Use Backend Proxy

Create a backend service that:

  1. Authenticates users
  2. Connects to Hercle WebSocket with your API key
  3. Forwards authorized messages to client

3. Implement Reconnection Logic

let reconnectAttempts = 0;
const maxReconnectAttempts = 10;
const reconnectDelay = 1000;

function connect() {
  const ws = new WebSocket('wss://api.hercle.com/ws');

## Security Best Practices

### 1. Never Expose Keys Client-Side

❌ **Don't do this:**

```javascript
// Never hardcode API keys in browser JavaScript
const apiKey = 'your_actual_api_key_here'; // EXPOSED!
const connection = new signalR.HubConnectionBuilder()
  .withUrl(hubUrl, { accessTokenFactory: () => apiKey })
  .build();

Do this instead:

// Use backend proxy or environment variables (Node.js only)
const apiKey = process.env.HERCLE_API_KEY;
const connection = new signalR.HubConnectionBuilder()
  .withUrl(hubUrl, { accessTokenFactory: () => apiKey })
  .build();

2. Use Backend Proxy for Browser Apps

For browser-based applications, create a backend service that:

  1. Authenticates your users
  2. Establishes SignalR connection with your API key
  3. Forwards authorized messages to clients
  4. Never exposes your API key to the browser

3. Implement Reconnection Logic

SignalR can automatically reconnect, but you should handle disconnections:

const connection = new signalR.HubConnectionBuilder()
  .withUrl(`${baseUrl}/${hubPath}`, {
    accessTokenFactory: () => apiKey,
  })
  .withAutomaticReconnect([0, 2000, 5000, 10000, 30000]) // Retry delays in ms
  .build();

// Handle reconnecting
connection.onreconnecting((error) => {
  console.log('Reconnecting...', error);
});

// Handle reconnected
connection.onreconnected((connectionId) => {
  console.log('Reconnected!', connectionId);
  // Resubscribe to channels if needed
});

// Handle closed (won't reconnect automatically)
connection.onclose((error) => {
  console.log('Connection closed:', error);
  // Implement manual reconnection if needed
});

4. Handle Maintenance Windows

Hercle performs scheduled maintenance every Wednesday from 10:00 AM to 11:00 AM GMT+1. During upgrades:

  • SignalR disconnections may occur
  • Implement reconnection logic with exponential backoff
  • Display maintenance notices to users

Recommended reconnection strategy:

// C# example from official documentation
connection.Closed += async (error) =>
{
    // Log the disconnection
    Console.WriteLine("Disconnected from SignalR. Attempting to reconnect...");

    // Wait random interval before reconnecting (0-5 seconds)
    await Task.Delay(new Random().Next(0, 5) * 1000);

    try {
        await connection.StartAsync();
        Console.WriteLine("Reconnected successfully");
    } catch (Exception ex) {
        Console.WriteLine($"Reconnection failed: {ex.Message}");
    }
};

For more details, see Microsoft's SignalR Connection Lifetime Events documentation.

5. Monitor Connection Health

// Check connection state
if (connection.state === signalR.HubConnectionState.Connected) {
  console.log('✓ Connected');
} else if (connection.state === signalR.HubConnectionState.Disconnected) {
  console.log('✗ Disconnected');
}

// Log all state changes
connection.onclose(() => console.log('State: Disconnected'));
connection.onreconnecting(() => console.log('State: Reconnecting'));
connection.onreconnected(() => console.log('State: Connected'));

Testing Authentication

Test your SignalR connection with this simple script:

JavaScript Test

const signalR = require('@microsoft/signalr');

const apiKey = 'YOUR_API_KEY';
const baseUrl = 'https://publicapi.sandbox.hercle.financial';
const hubPath = 'ExecutionLiveServer/v1';

const connection = new signalR.HubConnectionBuilder()
  .withUrl(`${baseUrl}/${hubPath}`, {
    accessTokenFactory: () => apiKey,
  })
  .build();

connection.on('executionEvents', (message) => {
  console.log('✅ Event received:', message);
  connection.stop();
});

connection
  .start()
  .then(() => {
    console.log('✅ Connected successfully!');
    // Test with GetPairs command
    return connection.invoke('GetPairs', ['test-client']);
  })
  .then(() => {
    console.log('✅ Command sent successfully!');
  })
  .catch((err) => {
    console.error('❌ Error:', err.message);
  });

Common Issues

Connection Timeout

Problem: Connection times out or fails to connect

Solutions:

  • Check your API key is valid and active
  • Verify the base URL and hub path are correct
  • Ensure your firewall allows WebSocket connections
  • Test network connectivity

"401 Unauthorized" Error

Problem: API key rejected during connection

Solutions:

  • Verify your API key is correct (no extra spaces)
  • Check if API key has been revoked or expired
  • Ensure KYC verification is complete
  • Confirm you're using the right environment (sandbox vs production)

Frequent Disconnections

Problem: Connection drops frequently

Solutions:

  • Implement automatic reconnection logic
  • Check network stability
  • Verify you're not exceeding connection limits
  • Be aware of scheduled maintenance windows (Wednesday 10-11 AM GMT+1)

Next Steps