Complete Guide to the HercleX API: REST, WebSocket & Webhooks
Introduction
The HercleX API is the technical infrastructure that allows developers and institutions to integrate Hercle platform capabilities directly into their own applications. Through this API you can execute OTC trades, manage deposits and withdrawals, monitor balances in real time, manage end users, and much more — all programmatically, without manually interacting with the web interface.
HercleX provides two types of APIs:
REST API: Traditional HTTP endpoints, ideal for server-side integrations and batch operations
WebSocket API (SignalR): Persistent, bidirectional connection for real-time data, price feeds, and instant notifications
This guide will walk you through everything you need to know to get started with the Hercle APIs — from setting up authentication to implementing webhooks, all the way to best practices for a stable and secure production integration.
Table of Contents
1. Environments: Sandbox vs Production
Before writing a single line of code, it's essential to understand the difference between the two environments HercleX provides.
Sandbox Environment
REST: https://publicapi.sandbox.hercle.financial WebSocket: https://publicapi.sandbox.hercle.financial/ExecutionLiveServer/v1 Auth: https://auth.sandbox.hercle.financial/sign-in
The Sandbox is designed exclusively for development, testing, and debugging. No real funds are involved, rate limits are more permissive, and KYC verification is not required. Data may be reset periodically.
Use the Sandbox to:
Test your integration without risking real funds
Develop and debug your code
Get familiar with endpoints and responses
Simulate scenarios like deposits (via the
/simulate-payinendpoint) or end user registration
Production Environment
REST: https://publicapi.hercle.financial WebSocket: https://publicapi.hercle.financial/ExecutionLiveServer/v1
The production environment is for live operations with real funds. It requires completed KYC verification, and standard rate limits apply. Data is permanently persisted.
⚠️ Important recommendation: Always thoroughly test your integration in the Sandbox before going live. A mistake in production can have real financial consequences.
2. Authentication: How to Get and Use Your API Key
All HercleX API requests — both REST and WebSocket — require authentication via an API Key.
How to Get Your API Key
Complete the onboarding process and get your KYC approved
Log in at
https://auth.sandbox.hercle.financial/sign-in(or the production portal)Navigate to the Account page
Go to the API Key section
Generate a new key or retrieve your existing one
Save your key immediately in a secure place — it will only be shown once
🔐 Warning: Your API Key is like a password. Never share it, never put it in your source code, and never publish it on GitHub or any other repository.
How to Use Your API Key (REST)
Every REST request must include the key in the Authorization header as a Bearer token:
Authorization: Bearer YOUR_API_KEY Content-Type: application/json
cURL example:
curl -X GET https://publicapi.sandbox.hercle.financial/api/v1/user/balances \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json"
JavaScript (Fetch) example:
const response = await fetch( 'https://publicapi.sandbox.hercle.financial/api/v1/user/balances', { method: 'GET', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', }, } ); const data = await response.json();
Python (Requests) example:
import requests headers = { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' } response = requests.get( 'https://publicapi.sandbox.hercle.financial/api/v1/user/balances', headers=headers ) data = response.json()
Authentication Errors
HTTP Code | Error | Likely Cause |
|---|---|---|
401 |
| Key missing, incorrect, expired, or missing the "Bearer " prefix |
403 |
| KYC not approved, insufficient permissions, account restrictions |
API Key Security Best Practices
Use environment variables — never hardcode the key in your code
Add
.envto your.gitignoreto avoid accidentally committing credentialsRotate keys regularly and revoke unused ones
Always use HTTPS — all requests must go over encrypted connections
Monitor API activity — regularly review usage patterns for anomalies
# .env file (add to .gitignore!) HERCLE_API_KEY=your_key_here HERCLE_BASE_URL=https://publicapi.sandbox.hercle.financial
// ✅ Correct: use environment variables const apiKey = process.env.HERCLE_API_KEY; // ❌ Wrong: never hardcode the key const apiKey = 'my_actual_key_123';
3. REST API: Overview and Features
The HercleX REST API follows standard RESTful principles: HTTP endpoints, GET/POST/DELETE methods, and JSON responses. It's the ideal choice for server-side integrations, batch processing, and operations that don't require real-time updates.
The current API version is v1, indicated in the base URL path: /api/v1/.
Endpoint Categories
📊 Balances & User Data
Retrieve account balances, trade history, and transaction history.
Endpoint | Description |
|---|---|
| Current balance for all assets |
| Paginated trade list |
| Paginated transaction list |
| Trades by specific ClientId |
Sample balance response:
[ { "userId": "1a66db8f-4043-4035-91df-615b3a7ac073", "sequence": 42, "assets": [ { "name": "BTC", "available": 1.5, "allocated": 0.5 }, { "name": "USDT", "available": 10000, "allocated": 2000 } ] } ]
💱 Trading Pairs & Pricing
Before placing an order, you need to know the available pairs and current price.
Endpoint | Description |
|---|---|
| All available trading pairs |
| Current price for a pair |
| Calculate size based on amount |
| Supported networks for an asset |
📋 OTC Orders
Endpoint | Description |
|---|---|
| Place an OTC order |
| Paginated order list |
| Orders by ClientId |
💬 RFQ (Request for Quote)
The RFQ flow lets you request a custom quote before proceeding with a buy or sell.
Endpoint | Description |
|---|---|
| Create a standard RFQ |
| Create an RFQ with custom expiration |
| Accept an RFQ |
📍 Address Management
Endpoint | Description |
|---|---|
| List whitelisted addresses |
| Add address to whitelist |
| Remove address from whitelist |
| List deposit addresses |
| Create deposit address |
💸 Withdrawals
Endpoint | Description |
|---|---|
| Create a standard withdrawal |
| Withdrawal with address params |
| Instant withdrawal |
| List withdrawals with various filter options |
⚡ Lightning Network
Endpoint | Description |
|---|---|
| Create a Lightning invoice |
| List Lightning invoices |
| Withdraw via Lightning Network |
🏦 Banking (Fiat)
The Banking section manages everything related to fiat flows: end users, deposit accounts, payees, and transfers.
Endpoint | Description |
|---|---|
| Register a business end user |
| Register an individual end user |
| Create a deposit account |
| Register a payee |
| Create a bank transfer |
| Get the transaction ledger |
📝 Note: Banking endpoints related to end users and deposit accounts are available in the Sandbox environment for testing. Check the documentation for production availability.
4. WebSocket API (SignalR): Real-Time Data
The HercleX WebSocket API uses Microsoft SignalR technology for persistent, bidirectional communication. It's the ideal choice for trading applications that require real-time price feeds, instant order updates, and immediate balance notifications.
Why SignalR?
SignalR offers significant advantages over a raw WebSocket: it automatically handles reconnection after network interruptions, guarantees message ordering, and supports multiple programming languages with dedicated client libraries.
Installing the Client Library
JavaScript (Node.js):
npm install @microsoft/signalr
Python:
pip install signalrcore
C#:
Install-Package Microsoft.AspNetCore.SignalR.Client
Establishing a Connection
JavaScript:
const signalR = require('@microsoft/signalr'); const connection = new signalR.HubConnectionBuilder() .withUrl('https://publicapi.sandbox.hercle.financial/ExecutionLiveServer/v1', { accessTokenFactory: () => 'YOUR_API_KEY', }) .withAutomaticReconnect() .build(); connection.start() .then(() => console.log('Connected to HercleX SignalR hub')) .catch(err => console.error('Connection failed:', err));
Python:
from signalrcore.hub_connection_builder import HubConnectionBuilder connection = HubConnectionBuilder()\ .with_url( 'https://publicapi.sandbox.hercle.financial/ExecutionLiveServer/v1', options={"access_token_factory": lambda: 'YOUR_API_KEY'} )\ .with_automatic_reconnect({ "type": "interval", "intervals": [0, 2000, 10000, 30000] })\ .build() connection.start()
Message Structure
All hub responses follow this standard format:
{ "channel": "executionEvents", "topic": "Execution", "subject": "execution.order.executed", "type": "message", "clientId": "your-client-id", "data": "{...}" }
The data field is a JSON string that must be separately parsed. The subject field indicates the event type.
Listening to Events
// Execution events (orders, trades, balances) connection.on('executionEvents', (message) => { const response = JSON.parse(message); const data = JSON.parse(response.data); switch (response.subject) { case 'execution.pairs': console.log('Available pairs:', data); break; case 'execution.order.executed': if (data.status === 1) { console.log('Order executed successfully:', data); } else if (data.status === 2) { console.error('Order rejected:', data.statusError); } break; case 'account.balances.snapshot': console.log('Balance updated:', data); break; case 'preChecksError': console.error('Validation error:', data.message); break; } }); // Real-time price feed connection.on('subscriptions', (message) => { const response = JSON.parse(message); if (response.subject === 'execution.feeds.priceUpdate') { const price = JSON.parse(response.data); console.log(`${price.pair}: Buy ${price.buyPrice} | Sell ${price.sellPrice}`); } });
Main WebSocket Methods
Hub Method | Description |
|---|---|
| List available pairs |
| Subscribe to real-time price feed |
| Unsubscribe from price feed |
| Place an OTC order |
| Create an RFQ |
| Accept an RFQ |
| Get account balances |
| Create a withdrawal |
| Add a whitelisted address |
| Create a bank transfer |
| Generate a Lightning invoice |
Handling Reconnection
Automatic reconnection is enabled by default with SignalR. However, it's important to re-subscribe to feeds after reconnecting, as previous subscriptions are lost.
const activeSubscriptions = new Map(); async function subscribe(clientId, pair, size) { await connection.invoke('SubscribePairPriceChannel', [clientId, pair, size]); activeSubscriptions.set(pair, { clientId, pair, size }); } // Re-subscribe automatically after reconnection connection.onreconnected(async () => { console.log('Reconnected! Restoring subscriptions...'); for (const [pair, sub] of activeSubscriptions) { await connection.invoke('SubscribePairPriceChannel', [sub.clientId, sub.pair, sub.size]); } }); connection.onreconnecting((error) => { console.log('Connection lost, reconnecting...', error); }); connection.onclose((error) => { console.error('Connection permanently closed:', error); });
5. Webhooks: Push Notifications for Account Events
Webhooks allow you to receive automatic real-time notifications whenever relevant events occur in your Hercle account — deposits, withdrawals, user status changes, balance updates, and much more.
Instead of continuously polling the API (which is inefficient and consumes rate limit), Hercle sends an HTTP POST request to your endpoint every time something important happens.
How It Works
You provide Hercle with your webhook endpoint URL
Hercle generates an RSA public/private key pair and shares the public key with you
On each event, Hercle signs the payload with the private key and sends it to your endpoint
Your server verifies the signature using the public key and processes the event
Your server responds with status
200 OKto confirm receipt
Payload Structure
Every webhook uses a standard envelope:
{ "EventId": "evt_abc123def456", "EventType": "Banking.Deposit.StatusUpdated", "Timestamp": "2025-01-15T14:30:00Z", "Data": "{...}" }
EventId: Unique event identifier (use for idempotency)
EventType: Type of event that occurred
Timestamp: When the event happened (ISO 8601)
Data: JSON string with event-specific data (must be parsed separately)
Webhook Request Headers
Header | Description |
|---|---|
| Base64-encoded RSA-SHA256 signature |
| Unix timestamp of when the request was signed |
| Unique delivery identifier (for debugging) |
|
|
Event Types
Banking Events (Fiat)
EventType | When it's sent |
|---|---|
| Deposit processed/completed |
| New withdrawal request created |
| Withdrawal status changed |
| New payout/transfer created |
| Payout status changed |
Other Events
EventType | When it's sent |
|---|---|
| New virtual account registered |
| Account balance changed |
| New end user registered |
| End user data updated |
| End user status changed |
| New payee registered |
| Payee address status changed |
Security: Signature Verification
This is the most critical step. Every webhook must be verified to ensure it genuinely comes from Hercle and not from a malicious actor. The verification process is:
Extract the
X-Webhook-SignatureandX-Webhook-TimestampheadersVerify the timestamp is within 5 minutes (protection against replay attacks)
Build the message:
timestamp.rawBody(e.g.1705329000.{"EventId":"..."})Compute the SHA-256 hash of the message
Verify the RSA-SHA256 signature using the public key provided by Hercle
Node.js (Express) implementation:
const express = require('express'); const crypto = require('crypto'); const app = express(); const PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY----- YOUR_PUBLIC_KEY_HERE -----END PUBLIC KEY-----`; // IMPORTANT: preserve the raw body before JSON parsing app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf.toString('utf8'); }, })); function verifyTimestamp(timestamp) { const ts = Number(timestamp); if (!Number.isFinite(ts)) return false; const diffMinutes = Math.abs((Date.now() / 1000 - ts) / 60); return diffMinutes <= 5; } function verifySignature(rawBody, signatureBase64, timestamp) { try { const message = `${timestamp}.${rawBody}`; const digest = crypto.createHash('sha256').update(message, 'utf8').digest(); const verifier = crypto.createVerify('RSA-SHA256'); verifier.update(digest); return verifier.verify(PUBLIC_KEY_PEM, signatureBase64, 'base64'); } catch (e) { return false; } } app.post('/webhooks/hercle', (req, res) => { const signature = req.get('X-Webhook-Signature') || ''; const timestamp = req.get('X-Webhook-Timestamp') || ''; const webhookId = req.get('X-Webhook-Id') || ''; if (!verifyTimestamp(timestamp)) { return res.status(400).json({ error: 'Timestamp invalid or expired' }); } if (!verifySignature(req.rawBody, signature, timestamp)) { return res.status(400).json({ error: 'Invalid signature' }); } // Log for debugging console.log(`Webhook received: ${req.body.EventType} [${webhookId}]`); // Respond with 200 IMMEDIATELY, then process asynchronously res.status(200).json({ success: true }); const eventData = JSON.parse(req.body.Data); processWebhookAsync(req.body.EventType, eventData, req.body.EventId) .catch(console.error); });
Python (Flask) implementation:
import hashlib, time from base64 import b64decode from flask import Flask, request, jsonify from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding app = Flask(__name__) PUBLIC_KEY_PEM = b"-----BEGIN PUBLIC KEY-----\nYOUR_PUBLIC_KEY\n-----END PUBLIC KEY-----" def verify_timestamp(ts_str): try: return abs(int(time.time()) - int(ts_str)) / 60 <= 5 except: return False def verify_signature(raw_body, sig_b64, timestamp): try: message = f"{timestamp}.{raw_body}".encode('utf-8') digest = hashlib.sha256(message).digest() pub_key = serialization.load_pem_public_key(PUBLIC_KEY_PEM) pub_key.verify(b64decode(sig_b64), digest, padding.PKCS1v15(), hashes.SHA256()) return True except: return False @app.route('/webhooks/hercle', methods=['POST']) def handle_webhook(): raw_body = request.get_data(as_text=True) payload = request.get_json() if not verify_timestamp(request.headers.get('X-Webhook-Timestamp', '')): return jsonify({'error': 'Expired timestamp'}), 400 if not verify_signature(raw_body, request.headers.get('X-Webhook-Signature', ''), request.headers.get('X-Webhook-Timestamp', '')): return jsonify({'error': 'Invalid signature'}), 400 return jsonify({'success': True}), 200
Webhook Best Practices
1. Respond quickly. Return 200 as soon as possible. If your handler takes time, process asynchronously (e.g. with a queue).
2. Implement idempotency. Use the EventId to avoid processing the same event twice:
const processedEvents = new Set(); // use a database in production! async function processWebhookAsync(eventType, data, eventId) { if (processedEvents.has(eventId)) { console.log('Event already processed:', eventId); return; } processedEvents.add(eventId); // Process the event... }
3. Handle retries. If your endpoint returns a non-2xx code, Hercle may retry the delivery. Make sure your system handles duplicate deliveries safely.
4. Log everything. Save the X-Webhook-Id for every webhook received. It's essential for debugging and for contacting support.
5. Always use HTTPS. Your webhook endpoint must be exposed over HTTPS to ensure data is encrypted in transit.
6. Error Handling
The HercleX API uses standard HTTP status codes and returns error details in JSON format:
{ "code": "ERROR_CODE", "message": "Detailed error description" }
HTTP Codes and Their Meaning
Code | Meaning |
|---|---|
| Request completed successfully |
| Resource successfully created |
| Invalid input or validation error |
| API Key missing or invalid |
| Insufficient permissions |
| Resource not found |
| Rate limit exceeded |
| Server-side error |
Application Error Codes
Code | HTTP | Description |
|---|---|---|
| 400 | Request validation failed |
| 400 | Resource already exists or conflict |
| 400 | General error during processing |
| 401 | Invalid authentication credentials |
| 403 | Insufficient permissions |
| 404 | Requested resource does not exist |
| 429 | Too many requests in a given period |
Error Handling in Code
async function callHercleAPI(endpoint, options = {}, retryCount = 0) { const response = await fetch( `https://publicapi.sandbox.hercle.financial${endpoint}`, { ...options, headers: { 'Authorization': `Bearer ${process.env.HERCLE_API_KEY}`, 'Content-Type': 'application/json', ...options.headers, }, } ); if (!response.ok) { const error = await response.json(); if (response.status === 429 && retryCount < 3) { const waitTime = Math.pow(2, retryCount) * 1000; await new Promise(resolve => setTimeout(resolve, waitTime)); return callHercleAPI(endpoint, options, retryCount + 1); } throw new Error(`API Error: ${error.code} - ${error.message}`); } return response.json(); }
7. Pagination
Endpoints that return lists support pagination to handle large amounts of data efficiently.
Offset-based Pagination
Many endpoints use path parameters for pagination:
GET /api/v1/orders/{startDate}/{endDate}/{page}/{pageSize}
Parameters:
page: Page number (zero-based: 0 = first page, 1 = second, etc.)pageSize: Items per page (min 5, max 50)startDate: Start date in ISO 8601 (e.g.2024-01-01T00:00:00Z)endDate: End date in ISO 8601
Example:
# First page, 50 items, January 2025 GET /api/v1/orders/2025-01-01T00:00:00Z/2025-01-31T23:59:59Z/0/50 # Second page GET /api/v1/orders/2025-01-01T00:00:00Z/2025-01-31T23:59:59Z/1/50
Cursor-based Pagination
Some endpoints (such as Banking ones) use a cursor token for pagination:
{ "payload": [ ... ], "paginationToken": "eyJwYXJ0aXRpb25LZXkiOnsiaWQiOiJldXNfZGVm..." }
To get the next page, pass the paginationToken in your next request. If the token is absent from the response, you've reached the last page.
8. Idempotency: Preventing Duplicate Operations
Some critical operations (withdrawals, orders, transfers) support idempotency via the Client-Id header. This mechanism ensures that in case of a network issue — where you don't know if the original request was processed — the server won't execute the operation twice.
How It Works
Include a unique Client-Id (e.g. a UUID) in your request header. If you send the same request with the same Client-Id within 6 hours, the server returns the original response without reprocessing the operation. After 6 hours, the Client-Id expires and can be reused.
curl -X POST https://publicapi.hercle.financial/api/v1/orders \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Client-Id: 550e8400-e29b-41d4-a716-446655440000" \ -d '{"priceId": "xxx", "side": "buy", "slippage": 1.0}'
Endpoints That Support Idempotency
POST /api/v1/orders— OTC OrderPOST /api/v1/rfq/accept— Accept RFQPOST /api/v1/withdrawals— WithdrawalPOST /api/v1/withdrawals/address— Withdrawal with address paramsPOST /api/v1/withdrawals/instant— Instant withdrawalPOST /api/v1/transfer— Bank transfer
⚠️ Note: Idempotency is currently available in the Sandbox environment only.
9. Rate Limiting and Scheduled Maintenance
Rate Limiting
The API implements rate limiting to ensure system stability. If you exceed the limit, you'll receive a 429 Too Many Requests error.
The best practice is to implement exponential backoff for retries:
async function makeRequestWithRetry(url, options, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { const response = await fetch(url, options); if (response.status === 429) { const waitTime = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s... console.log(`Rate limited. Waiting ${waitTime}ms...`); await new Promise(resolve => setTimeout(resolve, waitTime)); continue; } return response; } throw new Error('Max retries exceeded'); }
Scheduled Maintenance
HercleX has a scheduled maintenance window every Wednesday from 10:00 AM to 11:00 AM GMT+1. Temporary disconnections may occur during this window.
How to handle maintenance:
Implement automatic reconnection (SignalR handles this natively)
Add retry logic for REST calls
Monitor the status page for real-time updates on incidents and urgent maintenance
10. Integration Best Practices
Security
Never expose the API Key in source code or repositories
Use environment variables for all credentials
Rotate keys periodically
Use HTTPS exclusively
Monitor logs for unusual access patterns
Performance
Cache data that changes infrequently (e.g. the pairs list)
Use pagination instead of fetching all data at once
Implement exponential backoff to handle rate limiting gracefully
For real-time updates (prices, balances), use the WebSocket API instead of REST polling
Reliability
Always handle HTTP error codes and application error messages
Implement idempotency for critical transactional operations
Test retry and reconnection logic in the Sandbox
Handle maintenance windows with automatic retries
Log all critical operations with the unique IDs provided by the API
Testing
Always use the Sandbox environment for development and testing
Download the Postman collection from the GitHub repository to quickly test endpoints
Test edge cases: unstable network, timeouts, duplicate responses
Verify your webhook signature verification logic with test payloads before going live
11. Useful Resources
Resource | Link |
|---|---|
Full technical documentation | https://documentation.hercle.financial |
Postman Collection (GitHub) | https://github.com/JoaoHercle/hercle_documentation |
Service status page | https://status.hercle.financial |
Sandbox authentication portal | https://auth.sandbox.hercle.financial/sign-in |
Microsoft SignalR documentation | https://learn.microsoft.com/en-us/aspnet/signalr |
Need Help?
If you run into issues during your integration:
Check the full technical documentation for details on specific endpoints
Verify service status on the status page
Review your application logs — especially the
X-Webhook-Idheaders for webhooks andClientIdvalues for ordersContact the Hercle support team and provide the unique IDs of the operations involved (OrderId, EventId, X-Webhook-Id) to help speed up the investigation
This article is based on the official HercleX API documentation v1.0.19. For the most up-to-date information, always refer to the official documentation.