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
- Create SignalR hub connection with API key
- Start the connection
- Listen for events
- 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:
- Sandbox: Log in at https://auth.sandbox.hercle.financial/sign-in
- Production: Use the production authentication portal
- Navigate to the Account page
- Go to the API Key area
- 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++
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:
- Authenticates users
- Connects to Hercle WebSocket with your API key
- 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:
- Authenticates your users
- Establishes SignalR connection with your API key
- Forwards authorized messages to clients
- 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)