v1.0

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

  1. Obtain your API credentials from the Herald dashboard
  2. Configure your webhook endpoint to receive events
  3. Generate wallets using the Create Wallet API
  4. Receive wallet.created webhook 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
💡
Security Recommendation: While request signing is optional, we strongly recommend signing all requests in production to prevent tampering and replay attacks.

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:

Signature Payload Format
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"}
⚠️
Timestamp Window: Requests must be signed with a timestamp within 5 minutes of the server time. Ensure your system clock is synchronized.

Code Examples

JavaScript / Node.js
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);
Python
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)
cURL
# 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.

POST /api/v1/wallets/generate

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

JSON
{
    "user_id": "user_12345",
    "network_type": "ETH,BNB,POL",
    "threshold_scheme": "2-of-3"
}

Response

Returns 202 Accepted with a job ID for tracking.

JSON Response
{
    "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

JSON
{
    "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

wallet.created

Sent when a wallet is successfully generated

JSON Payload
{
    "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"
            }
        ]
    }
}
deposit.detected

Sent when an incoming deposit is detected

JSON Payload
{
    "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
    }
}
sweep.completed

Sent when funds are swept to the treasury

JSON Payload
{
    "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"
    }
}
wallet.generation_failed

Sent when wallet generation fails

JSON Payload
{
    "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:

Signature Payload Format
TIMESTAMP.RAW_BODY

Verification Example

JavaScript / Node.js
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 });
}
Python
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
⚠️
Important: Always verify the timestamp to prevent replay attacks. Reject webhooks with timestamps older than 5 minutes.

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

JSON Error Response
{
    "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
💡
EVM Compatibility: ETH, BNB, and POL wallets share the same address format. A single wallet generation can create addresses for all EVM-compatible networks.