Skip to main content

Documentation Index

Fetch the complete documentation index at: https://apidocs.hopnow.io/llms.txt

Use this file to discover all available pages before exploring further.

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.

How HMAC Authentication Works

Each API request must include four authentication headers:
HeaderDescription
X-API-KeyYour API Key ID
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://api.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://api.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 HopClient:
    def __init__(self, api_key_id, api_secret, base_url="https://api.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 = HopClient("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 HopClient {
  constructor(apiKeyId, apiSecret, baseUrl = 'https://api.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 HopClient('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://api.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 API key identifier used to identify your account.
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://api.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.