> ## 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.

# Authentication

> Learn how to authenticate your API requests using HMAC signatures

## 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)

<Info>
  All API endpoints require HMAC authentication.
</Info>

## How HMAC Authentication Works

Each API request must include four authentication headers:

| Header        | Description                                          |
| ------------- | ---------------------------------------------------- |
| `X-API-Key`   | The `api_key` value returned when the key is created |
| `X-Signature` | HMAC-SHA256 signature of the request                 |
| `X-Timestamp` | Unix timestamp when the request was created          |
| `X-Nonce`     | Unique random token to prevent replay attacks        |

The API uses only HMAC headers for client API requests. Do not send an `Authorization: Bearer` header for these endpoints.

## Creating the Signature

Use the credential pair returned when you create an API key:

* `api_key`: send this value in the `X-API-Key` header
* `secret`: use this value as the HMAC-SHA256 signing key

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}{PATH_AND_QUERY}{TIMESTAMP}{NONCE}{BODY}
```

Where:

* **HTTP\_METHOD**: Uppercase HTTP method (`GET`, `POST`, `PATCH`, `DELETE`)
* **PATH\_AND\_QUERY**: The URL path exactly as sent to HopNow, starting with `/`, followed by `?` and the query string when the request has query parameters. Do not include the scheme, host, port, or fragment.
* **TIMESTAMP**: Unix timestamp as a string
* **NONCE**: Unique random token (32 hex characters)
* **BODY**: Exact request body string sent over the wire (empty string for GET requests)

Examples:

* Request URL `https://api.hopnow.io/v1/customers/cus_abc123/accounts` signs `/v1/customers/cus_abc123/accounts`
* Request URL `https://api.hopnow.io/v1/customers/cus_abc123/accounts?page=1&size=10` signs `/v1/customers/cus_abc123/accounts?page=1&size=10`

### Step-by-Step Example

Let's create a signature for this request:

```bash theme={null}
POST https://api.hopnow.io/v1/customers/cus_abc123/accounts
Content-Type: application/json

{"name":"Trading Account"}
```

**Step 1**: Generate timestamp and nonce

```python theme={null}
import time
import secrets

timestamp = int(time.time())
nonce = secrets.token_hex(16)
```

**Step 2**: Construct the payload

```
POST/v1/customers/cus_abc123/accounts1640995200a1b2c3d4e5f678901234567890abcdef{"name":"Trading Account"}
```

**Step 3**: Create HMAC-SHA256 signature

```python theme={null}
import hmac
import hashlib
import time
import secrets

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

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

print(signature)
```

## Implementation Examples

### Python

<CodeGroup>
  ```python Complete Example theme={null}
  import requests
  import hmac
  import hashlib
  import time
  import secrets
  import json

  class HopClient:
      def __init__(self, api_key, secret, base_url="https://api.hopnow.io"):
          self.api_key = api_key
          self.secret = secret
          self.base_url = base_url
      
      def _create_signature(self, method, path_and_query, timestamp, nonce, body=""):
          """Create HMAC signature for request authentication"""
          payload = f"{method.upper()}{path_and_query}{timestamp}{nonce}{body}"
          signature = hmac.new(
              self.secret.encode('utf-8'),
              payload.encode('utf-8'),
              hashlib.sha256
          ).hexdigest()
          return signature
      
      def _make_request(self, method, path_and_query, data=None):
          """Make authenticated request to the API"""
          url = f"{self.base_url}{path_and_query}"
          timestamp = str(int(time.time()))
          nonce = secrets.token_hex(16)
          body = json.dumps(data, separators=(",", ":")) if data else ""
          
          signature = self._create_signature(method, path_and_query, timestamp, nonce, body)
          
          headers = {
              "Content-Type": "application/json",
              "X-API-Key": self.api_key,
              "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", "your_secret")
  account = client.create_account("cus_abc123", "My Account")
  print(account)
  ```

  ```python Simple Example theme={null}
  import requests
  import hmac
  import hashlib
  import time
  import secrets
  import json

  # Configuration
  API_KEY = "your_api_key"
  SECRET = "your_secret"
  BASE_URL = "https://api.hopnow.io"

  def make_authenticated_request(method, path_and_query, data=None):
      url = f"{BASE_URL}{path_and_query}"
      timestamp = str(int(time.time()))
      nonce = secrets.token_hex(16)
      body = json.dumps(data, separators=(",", ":")) if data else ""
      
      # Create signature
      payload = f"{method.upper()}{path_and_query}{timestamp}{nonce}{body}"
      signature = hmac.new(
          SECRET.encode(),
          payload.encode(),
          hashlib.sha256
      ).hexdigest()
      
      # Make request
      headers = {
          "Content-Type": "application/json",
          "X-API-Key": API_KEY,
          "X-Timestamp": timestamp,
          "X-Nonce": nonce,
          "X-Signature": signature,
      }
      
      response = requests.request(method, url, headers=headers, data=body)
      return response.json()

  # Example usage
  result = make_authenticated_request(
      "POST",
      "/v1/customers/cus_abc123/accounts",
      {"name": "My Account"}
  )
  print(result)
  ```
</CodeGroup>

### JavaScript/Node.js

<CodeGroup>
  ```javascript Complete Example theme={null}
  const crypto = require('crypto');
  const axios = require('axios');

  class HopClient {
    constructor(apiKey, secret, baseUrl = 'https://api.hopnow.io') {
      this.apiKey = apiKey;
      this.secret = secret;
      this.baseUrl = baseUrl;
    }

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

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

    async makeRequest(method, pathAndQuery, data = null) {
      const url = `${this.baseUrl}${pathAndQuery}`;
      const timestamp = Math.floor(Date.now() / 1000).toString();
      const nonce = this.generateNonce();
      const body = data ? JSON.stringify(data) : '';
      
      const signature = this.createSignature(method, pathAndQuery, timestamp, nonce, body);
      
      const headers = {
        'Content-Type': 'application/json',
        'X-API-Key': this.apiKey,
        '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', 'your_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();
  ```

  ```javascript Simple Example theme={null}
  const crypto = require('crypto');
  const axios = require('axios');

  // Configuration
  const API_KEY = 'your_api_key';
  const SECRET = 'your_secret';
  const BASE_URL = 'https://api.hopnow.io';

  async function makeAuthenticatedRequest(method, pathAndQuery, data = null) {
    const url = `${BASE_URL}${pathAndQuery}`;
    const timestamp = Math.floor(Date.now() / 1000).toString();
    const nonce = crypto.randomBytes(16).toString('hex');
    const body = data ? JSON.stringify(data) : '';
    
    // Create signature
    const payload = `${method.toUpperCase()}${pathAndQuery}${timestamp}${nonce}${body}`;
    const signature = crypto
      .createHmac('sha256', SECRET)
      .update(payload)
      .digest('hex');
    
    // Make request
    const response = await axios({
      method,
      url,
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': API_KEY,
        'X-Timestamp': timestamp,
        'X-Nonce': nonce,
        'X-Signature': signature,
      },
      data: body || undefined
    });
    
    return response.data;
  }

  // Example usage
  makeAuthenticatedRequest(
    'POST',
    '/v1/customers/cus_abc123/accounts',
    { name: 'My Account' }
  ).then(result => {
    console.log(result);
  }).catch(error => {
    console.error('Error:', error.response?.data || error.message);
  });
  ```
</CodeGroup>

### cURL

```bash theme={null}
#!/bin/bash

# Configuration
API_KEY="your_api_key"
SECRET="your_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}${ENDPOINT}${TIMESTAMP}${NONCE}${BODY}"
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" -hex | cut -d' ' -f2)

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

## Authentication Headers Reference

### X-API-Key

The `api_key` value returned when you create an API key. This identifies the key used for the request.

```
X-API-Key: 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
```

### 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: a1b2c3d4e5f678901234567890abcdef
```

## Security Best Practices

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

### 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

```json theme={null}
{
  "error": {
    "type": "authentication_error",
    "code": "invalid_signature",
    "message": "The signature is invalid"
  }
}
```

**Common causes:**

* Incorrect payload construction (ensure format: `{METHOD}{PATH_AND_QUERY}{TIMESTAMP}{NONCE}{BODY}`)
* Signing the full URL instead of only the path and query string
* Wrong HTTP method case (must be uppercase)
* Missing or incorrect timestamp/nonce
* Query string ordering or encoding differences between the signed payload and the actual request
* Incorrect secret key

### Timestamp Too Old Error

```json theme={null}
{
  "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

```json theme={null}
{
  "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

```json theme={null}
{
  "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 a real GET endpoint to verify your authentication implementation. For example, list one account for a customer you own:

```bash theme={null}
GET https://api.hopnow.io/v1/customers/{customer_id}/accounts?size=1
```

The signed path for that request is:

```text theme={null}
/v1/customers/{customer_id}/accounts?size=1
```

***

Need help with authentication? Review the examples above or [contact support](mailto:support@hopnow.io).
