Skip to main content

Overview

The HopNow API uses HMAC (Hash-based Message Authentication Code) signatures to authenticate requests. This method ensures that:
  • Requests come from a verified source (authentication)
  • Request data hasn’t been tampered with (integrity)
  • Requests can’t be replayed (protection against replay attacks)
All API endpoints require HMAC authentication. There are no public endpoints.

How HMAC Authentication Works

Each API request must include four authentication headers:
HeaderDescription
X-API-KeyYour API Key ID (public identifier)
X-SignatureHMAC-SHA256 signature of the request
X-TimestampUnix timestamp when the request was created
X-NonceUnique random token to prevent replay attacks

Creating the Signature

The HMAC signature is created by:
  1. Constructing the payload from request components
  2. Creating an HMAC-SHA256 hash using your API secret
  3. Converting to hexadecimal representation

Signature Payload Format

The payload for signing consists of these components concatenated together:
{HTTP_METHOD}{URL}{TIMESTAMP}{NONCE}{BODY}
Where:
  • HTTP_METHOD: Uppercase HTTP method (GET, POST, PATCH, DELETE)
  • URL: Complete URL including path and query parameters
  • TIMESTAMP: Unix timestamp as a string
  • NONCE: Unique random token (32 hex characters)
  • BODY: Request body as a string (empty string for GET requests)

Step-by-Step Example

Let’s create a signature for this request:
POST https://apis.hopnow.io/v1/customers/cus_abc123/accounts
Content-Type: application/json

{
  "name": "Trading Account"
}
Step 1: Generate timestamp and nonce
import time
import secrets

timestamp = int(time.time())
nonce = secrets.token_hex(16)
Step 2: Construct the payload
POSThttps://apis.hopnow.io/v1/customers/cus_abc123/accounts1640995200a1b2c3d4e5f6789012345678901234567890abcdef{"name":"Trading Account"}
Step 3: Create HMAC-SHA256 signature
import hmac
import hashlib
import time
import secrets

timestamp = int(time.time())
nonce = secrets.token_hex(16)
payload = f"POST/v1/customers/cus_abc123/accounts{timestamp}{nonce}" + '{"name":"Trading Account"}'

signature = hmac.new(
    api_secret.encode('utf-8'),
    payload.encode('utf-8'),
    hashlib.sha256
).hexdigest()

print(signature)
# Output: 8f7e1b2c3d4e5f6789abcdef012345678901234567890abcdef1234567890ab

Implementation Examples

Python

import requests
import hmac
import hashlib
import time
import secrets
import json

class GlobalTransferClient:
    def __init__(self, api_key_id, api_secret, base_url="https://apis.hopnow.io"):
        self.api_key_id = api_key_id
        self.api_secret = api_secret
        self.base_url = base_url
    
    def _create_signature(self, method, url, timestamp, nonce, body=""):
        """Create HMAC signature for request authentication"""
        payload = f"{method.upper()}{url}{timestamp}{nonce}{body}"
        signature = hmac.new(
            self.api_secret.encode('utf-8'),
            payload.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
        return signature
    
    def _make_request(self, method, endpoint, data=None):
        """Make authenticated request to the API"""
        url = f"{self.base_url}{endpoint}"
        timestamp = str(int(time.time()))
        nonce = secrets.token_hex(16)
        body = json.dumps(data) if data else ""
        
        signature = self._create_signature(method, url, timestamp, nonce, body)
        
        headers = {
            "Content-Type": "application/json",
            "X-API-Key": self.api_key_id,
            "X-Timestamp": timestamp,
            "X-Nonce": nonce,
            "X-Signature": signature,
        }
        
        response = requests.request(method, url, headers=headers, data=body)
        response.raise_for_status()
        return response.json()
    
    def create_account(self, customer_id, name):
        """Create a new account"""
        return self._make_request(
            "POST",
            f"/v1/customers/{customer_id}/accounts",
            {"name": name}
        )
    
    def get_accounts(self, customer_id):
        """List customer accounts"""
        return self._make_request(
            "GET", 
            f"/v1/customers/{customer_id}/accounts"
        )

# Usage
client = GlobalTransferClient("your_api_key_id", "your_api_secret")
account = client.create_account("cus_abc123", "My Account")
print(account)

JavaScript/Node.js

const crypto = require('crypto');
const axios = require('axios');

class GlobalTransferClient {
  constructor(apiKeyId, apiSecret, baseUrl = 'https://apis.hopnow.io') {
    this.apiKeyId = apiKeyId;
    this.apiSecret = apiSecret;
    this.baseUrl = baseUrl;
  }

  createSignature(method, url, timestamp, nonce, body = '') {
    const payload = `${method.toUpperCase()}${url}${timestamp}${nonce}${body}`;
    return crypto
      .createHmac('sha256', this.apiSecret)
      .update(payload)
      .digest('hex');
  }

  generateNonce() {
    return crypto.randomBytes(16).toString('hex');
  }

  async makeRequest(method, endpoint, data = null) {
    const url = `${this.baseUrl}${endpoint}`;
    const timestamp = Math.floor(Date.now() / 1000).toString();
    const nonce = this.generateNonce();
    const body = data ? JSON.stringify(data) : '';
    
    const signature = this.createSignature(method, url, timestamp, nonce, body);
    
    const headers = {
      'Content-Type': 'application/json',
      'X-API-Key': this.apiKeyId,
      'X-Timestamp': timestamp,
      'X-Nonce': nonce,
      'X-Signature': signature,
    };

    try {
      const response = await axios({
        method,
        url,
        headers,
        data: body || undefined
      });
      return response.data;
    } catch (error) {
      throw new Error(`API request failed: ${error.response?.data?.message || error.message}`);
    }
  }

  async createAccount(customerId, name) {
    return this.makeRequest(
      'POST',
      `/v1/customers/${customerId}/accounts`,
      { name }
    );
  }

  async getAccounts(customerId) {
    return this.makeRequest('GET', `/v1/customers/${customerId}/accounts`);
  }
}

// Usage
const client = new GlobalTransferClient('your_api_key_id', 'your_api_secret');

async function example() {
  try {
    const account = await client.createAccount('cus_abc123', 'My Account');
    console.log('Created account:', account);
  } catch (error) {
    console.error('Error:', error.message);
  }
}

example();

cURL

#!/bin/bash

# Configuration
API_KEY_ID="your_api_key_id"
API_SECRET="your_api_secret"
BASE_URL="https://apis.hopnow.io"

# Request details
METHOD="POST"
ENDPOINT="/v1/customers/cus_abc123/accounts"
URL="${BASE_URL}${ENDPOINT}"
TIMESTAMP=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY='{"name":"My Account"}'

# Create signature
PAYLOAD="${METHOD}${URL}${TIMESTAMP}${NONCE}${BODY}"
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$API_SECRET" -hex | cut -d' ' -f2)

# Make request
curl -X "$METHOD" "$URL" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY_ID" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE" \
  -d "$BODY"

Authentication Headers Reference

X-API-Key

Your public API key identifier. This is safe to include in client-side code.
X-API-Key: pk_test_1234567890abcdef

X-Signature

The HMAC-SHA256 signature of your request, encoded as a lowercase hexadecimal string.
X-Signature: 8f7e1b2c3d4e5f6789abcdef012345678901234567890abcdef1234567890ab

X-Timestamp

Unix timestamp indicating when the request was created. Requests older than 5 minutes are rejected.
X-Timestamp: 1640995200

X-Nonce

Unique random token to prevent replay attacks. Must be 32 hexadecimal characters (16 bytes).
X-Nonce: a1b2c3d4e5f6789012345678901234567890abcdef

Security Best Practices

Never expose your API secret: Keep your API secret secure and never include it in client-side code, logs, or version control.

1. Secure Storage

  • Store API secrets in environment variables or secure key management systems
  • Never hardcode secrets in your application code
  • Use different API keys for different environments (development, staging, production)

2. Request Validation

  • Always generate a new nonce for each request to prevent replay attacks
  • Ensure nonces are cryptographically random (use secrets.token_hex() in Python or crypto.randomBytes() in Node.js)
  • Validate timestamps to prevent replay attacks beyond the nonce window
  • Implement request timeouts to avoid hanging requests
  • Log authentication failures for security monitoring

3. Error Handling

  • Don’t expose authentication details in error messages
  • Implement proper retry logic with exponential backoff
  • Monitor for unusual authentication patterns

Common Issues and Troubleshooting

Invalid Signature Error

{
  "error": {
    "type": "authentication_error",
    "code": "invalid_signature",
    "message": "The signature is invalid"
  }
}
Common causes:
  • Incorrect payload construction (ensure format: {METHOD}{URL}{TIMESTAMP}{NONCE}{BODY})
  • Wrong HTTP method case (must be uppercase)
  • Missing or incorrect timestamp/nonce
  • URL encoding issues
  • Incorrect secret key

Timestamp Too Old Error

{
  "error": {
    "type": "authentication_error",
    "code": "timestamp_too_old",
    "message": "Request timestamp is too old"
  }
}
Solution: Ensure your system clock is synchronized and create fresh timestamps for each request.

Nonce Replay Error

{
  "error": {
    "type": "authentication_error",
    "code": "nonce_replay",
    "message": "Nonce already used"
  }
}
Solution: Generate a new unique nonce for each request. Nonces cannot be reused within the 5-minute window.

Missing Headers Error

{
  "error": {
    "type": "authentication_error",
    "code": "missing_headers", 
    "message": "Required authentication headers are missing"
  }
}
Solution: Ensure all four headers (X-API-Key, X-Signature, X-Timestamp, X-Nonce) are included in every request.

Testing Your Implementation

Use this test endpoint to verify your authentication implementation:
GET https://apis.hopnow.io/v1/auth/test
This endpoint returns your customer information if authentication is successful:
{
  "customer_id": "cus_abc123",
  "api_key_id": "pk_test_1234567890abcdef",
  "timestamp": 1640995200,
  "message": "Authentication successful"
}

Need help with authentication? Review the examples above or contact support.