Herald API Documentation
One Platform. Fast Integration. Herald enables businesses to generate secure MPC wallets and manage crypto deposits with complete visibility and reliable webhook notifications.
Secure by Design
MPC threshold signatures with enterprise-grade security
Herald uses Multi-Party Computation (MPC) to generate wallets with distributed key shares. No single point of failure - your assets are protected by cryptographic threshold schemes.
Base URL
https://mykxtqnal.herald.exchange/api/v1
Quick Start
- Obtain your API credentials from the Herald dashboard
- Configure your webhook endpoint to receive events
- Generate wallets using the Create Wallet API
- Receive
wallet.createdwebhook when ready
Authentication
All API requests require authentication using your API credentials. Include these headers with every request:
| Header | Required | Description |
|---|---|---|
X-API-Key |
Required | Your unique API key identifier |
X-API-Secret |
Required | Your API secret for authentication |
X-API-Timestamp |
Optional | Unix timestamp in seconds (required if signing requests) |
X-API-Signature |
Optional | HMAC-SHA256 request signature |
Content-Type |
Required | Must be application/json |
Request Signing
Sign your requests using HMAC-SHA256 for enhanced security. The signature proves the request originated from you and hasn't been modified in transit.
Signature Algorithm
Build the signature payload by concatenating these components with newline characters:
METHOD\nPATH\nTIMESTAMP\nBODY
| Component | Description | Example |
|---|---|---|
METHOD |
HTTP method in uppercase | POST |
PATH |
Full request path including query string | /api/v1/wallets/generate |
TIMESTAMP |
Unix timestamp in seconds | 1702300800 |
BODY |
JSON request body (or {} if empty) |
{"user_id":"user_123"} |
Code Examples
const crypto = require('crypto');
const axios = require('axios');
const API_KEY = 'your_api_key';
const API_SECRET = 'your_api_secret';
const BASE_URL = 'https://mykxtqnal.herald.exchange';
function generateSignature(method, path, timestamp, body) {
const payload = `${method}\n${path}\n${timestamp}\n${body}`;
return crypto
.createHmac('sha256', API_SECRET)
.update(payload)
.digest('hex');
}
async function createWallet(userId, networks) {
const method = 'POST';
const path = '/api/v1/wallets/generate';
const timestamp = Math.floor(Date.now() / 1000).toString();
const body = JSON.stringify({
user_id: userId,
network_type: networks.join(',')
});
const signature = generateSignature(method, path, timestamp, body);
const response = await axios.post(
`${BASE_URL}${path}`,
JSON.parse(body),
{
headers: {
'X-API-Key': API_KEY,
'X-API-Secret': API_SECRET,
'X-API-Timestamp': timestamp,
'X-API-Signature': signature,
'Content-Type': 'application/json'
}
}
);
return response.data;
}
// Usage
createWallet('user_123', ['ETH', 'BNB'])
.then(console.log)
.catch(console.error);
import hmac
import hashlib
import time
import json
import requests
API_KEY = 'your_api_key'
API_SECRET = 'your_api_secret'
BASE_URL = 'https://mykxtqnal.herald.exchange'
def generate_signature(method, path, timestamp, body):
payload = f"{method}\n{path}\n{timestamp}\n{body}"
signature = hmac.new(
API_SECRET.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signature
def create_wallet(user_id, networks):
method = 'POST'
path = '/api/v1/wallets/generate'
timestamp = str(int(time.time()))
body_dict = {
'user_id': user_id,
'network_type': ','.join(networks)
}
body = json.dumps(body_dict, separators=(',', ':'))
signature = generate_signature(method, path, timestamp, body)
headers = {
'X-API-Key': API_KEY,
'X-API-Secret': API_SECRET,
'X-API-Timestamp': timestamp,
'X-API-Signature': signature,
'Content-Type': 'application/json'
}
response = requests.post(
f'{BASE_URL}{path}',
json=body_dict,
headers=headers
)
return response.json()
# Usage
result = create_wallet('user_123', ['ETH', 'BNB'])
print(result)
# Generate signature using bash
TIMESTAMP=$(date +%s)
METHOD="POST"
PATH="/api/v1/wallets/generate"
BODY='{"user_id":"user_123","network_type":"ETH,BNB"}'
API_SECRET="your_api_secret"
PAYLOAD="${METHOD}\n${PATH}\n${TIMESTAMP}\n${BODY}"
SIGNATURE=$(echo -n -e "$PAYLOAD" | openssl dgst -sha256 -hmac "$API_SECRET" | cut -d' ' -f2)
# Make the API request
curl -X POST "https://mykxtqnal.herald.exchange/api/v1/wallets/generate" \
-H "Content-Type: application/json" \
-H "X-API-Key: your_api_key" \
-H "X-API-Secret: your_api_secret" \
-H "X-API-Timestamp: $TIMESTAMP" \
-H "X-API-Signature: $SIGNATURE" \
-d '{"user_id":"user_123","network_type":"ETH,BNB"}'
Create Wallet
Generate a new MPC wallet for a user. Wallets are created asynchronously - you'll receive a job ID immediately and a webhook when the wallet is ready.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
user_id |
string | Required | Unique identifier for the user in your system |
network_type |
string | Required | Comma-separated network codes: ETH, BNB, POL, SOL, TRX, BTC |
threshold_scheme |
string | Optional | MPC threshold scheme: "2-of-2", "2-of-3" (default), "3-of-5" |
Example Request
{
"user_id": "user_12345",
"network_type": "ETH,BNB,POL",
"threshold_scheme": "2-of-3"
}
Response
Returns 202 Accepted with a job ID for tracking.
{
"success": true,
"data": {
"job_id": "job_a1b2c3d4e5f6",
"status": "pending",
"networks": ["ETH", "BNB", "POL"],
"threshold_scheme": "2-of-3",
"created_at": "2024-01-15T10:30:00.000Z"
}
}
Webhook Events
Herald sends webhook notifications to your configured endpoint when events occur. Configure your webhook URL in the Herald dashboard.
Webhook Payload Structure
{
"event_id": "evt_abc123def456",
"event_type": "wallet.created",
"project_id": "proj_xyz789",
"timestamp": "2024-01-15T10:30:15.000Z",
"data": {
// Event-specific payload
}
}
Webhook Headers
| Header | Description |
|---|---|
X-Webhook-ID |
Unique identifier for this webhook delivery |
X-Webhook-Timestamp |
Unix timestamp when the webhook was sent |
X-Webhook-Signature |
HMAC-SHA256 signature (format: v1=<hex>) |
Event Types
Sent when a wallet is successfully generated
{
"event_id": "evt_wallet_created_001",
"event_type": "wallet.created",
"project_id": "proj_xyz789",
"timestamp": "2024-01-15T10:30:15.000Z",
"data": {
"job_id": "job_a1b2c3d4e5f6",
"user_id": "user_12345",
"wallets": [
{
"network": "ETH",
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f...",
"threshold_scheme": "2-of-3"
},
{
"network": "BNB",
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f...",
"threshold_scheme": "2-of-3"
}
]
}
}
Sent when an incoming deposit is detected
{
"event_id": "evt_deposit_001",
"event_type": "deposit.detected",
"project_id": "proj_xyz789",
"timestamp": "2024-01-15T12:45:00.000Z",
"data": {
"user_id": "user_12345",
"network": "ETH",
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f...",
"amount": "1.5",
"token": "ETH",
"tx_hash": "0xabc123...",
"confirmations": 12
}
}
Sent when funds are swept to the treasury
{
"event_id": "evt_sweep_001",
"event_type": "sweep.completed",
"project_id": "proj_xyz789",
"timestamp": "2024-01-15T13:00:00.000Z",
"data": {
"sweep_id": "sweep_abc123",
"user_id": "user_12345",
"network": "ETH",
"from_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f...",
"to_address": "0xTreasury...",
"amount": "1.495",
"token": "ETH",
"tx_hash": "0xdef456...",
"gas_used": "0.005"
}
}
Sent when wallet generation fails
{
"event_id": "evt_wallet_failed_001",
"event_type": "wallet.generation_failed",
"project_id": "proj_xyz789",
"timestamp": "2024-01-15T10:31:00.000Z",
"data": {
"job_id": "job_failed_xyz",
"user_id": "user_12345",
"error_code": "MPC_KEY_GENERATION_FAILED",
"error_message": "Failed to generate MPC key shares"
}
}
Webhook Signature Verification
Always verify webhook signatures to ensure requests are from Herald and haven't been tampered with.
Verification Algorithm
The signature is computed using HMAC-SHA256 with a different payload format than API requests:
TIMESTAMP.RAW_BODY
Verification Example
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, timestamp, secretKey) {
// Build the signature payload
const signaturePayload = `${timestamp}.${payload}`;
// Calculate expected signature
const expectedSignature = crypto
.createHmac('sha256', secretKey)
.update(signaturePayload)
.digest('hex');
// Compare with received signature (remove 'v1=' prefix)
const receivedSig = signature.replace('v1=', '');
// Use timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(receivedSig)
);
}
// Express.js middleware example
function webhookHandler(req, res) {
const signature = req.headers['x-webhook-signature'];
const timestamp = req.headers['x-webhook-timestamp'];
const payload = JSON.stringify(req.body);
// Verify timestamp is within 5 minutes
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
return res.status(401).json({ error: 'Timestamp expired' });
}
if (!verifyWebhookSignature(payload, signature, timestamp, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process the webhook event
const event = req.body;
console.log('Received event:', event.event_type);
res.status(200).json({ received: true });
}
import hmac
import hashlib
import time
from flask import Flask, request, jsonify
WEBHOOK_SECRET = 'your_webhook_secret'
def verify_webhook_signature(payload, signature, timestamp, secret_key):
# Build the signature payload
signature_payload = f"{timestamp}.{payload}"
# Calculate expected signature
expected_signature = hmac.new(
secret_key.encode('utf-8'),
signature_payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Compare with received signature (remove 'v1=' prefix)
received_sig = signature.replace('v1=', '')
# Use constant-time comparison
return hmac.compare_digest(expected_signature, received_sig)
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook_handler():
signature = request.headers.get('X-Webhook-Signature')
timestamp = request.headers.get('X-Webhook-Timestamp')
payload = request.get_data(as_text=True)
# Verify timestamp is within 5 minutes
now = int(time.time())
if abs(now - int(timestamp)) > 300:
return jsonify({'error': 'Timestamp expired'}), 401
if not verify_webhook_signature(payload, signature, timestamp, WEBHOOK_SECRET):
return jsonify({'error': 'Invalid signature'}), 401
# Process the webhook event
event = request.json
print(f"Received event: {event['event_type']}")
return jsonify({'received': True}), 200
Error Codes
Herald uses standard HTTP status codes and returns detailed error information.
| HTTP Status | Error Code | Description |
|---|---|---|
400 |
MISSING_FIELDS |
Required fields are missing from the request |
400 |
INVALID_NETWORK |
Unsupported or invalid network specified |
401 |
UNAUTHORIZED |
Missing API key or secret header |
401 |
INVALID_API_KEY |
API key or secret is invalid |
401 |
INVALID_SIGNATURE |
Request signature verification failed |
403 |
PROJECT_INACTIVE |
Your project is suspended or inactive |
403 |
IP_NOT_WHITELISTED |
Request IP is not in your whitelist |
404 |
NOT_FOUND |
Requested resource does not exist |
429 |
RATE_LIMIT_EXCEEDED |
Too many requests - slow down |
500 |
INTERNAL_ERROR |
Server error - contact support |
Error Response Format
{
"success": false,
"error": {
"code": "INVALID_API_KEY",
"message": "The provided API key is invalid or expired"
}
}
Supported Networks
Herald supports wallet generation on the following blockchain networks:
| Network Code | Blockchain | Native Token | Tokens Supported |
|---|---|---|---|
ETH |
Ethereum | ETH | USDT, USDC, ERC-20 tokens |
BNB |
BNB Smart Chain | BNB | USDT, USDC, BEP-20 tokens |
POL |
Polygon | POL (MATIC) | USDT, USDC, ERC-20 tokens |
SOL |
Solana | SOL | USDT, USDC, SPL tokens |
TRX |
TRON | TRX | USDT, USDC, TRC-20 tokens |
BTC |
Bitcoin | BTC | Native BTC only |