> ## Documentation Index
> Fetch the complete documentation index at: https://docs.moduluslabs.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> API key provisioning and authentication methods for HTTP and WebSocket protocols

## Overview

The Terminal Gateway uses API key-based authentication for both HTTP and WebSocket protocols. Each protocol has a different authentication mechanism:

| Protocol      | Authentication Method                                     |
| ------------- | --------------------------------------------------------- |
| HTTP API      | HMAC-SHA256 signature per request                         |
| WebSocket API | API key in headers or query parameters at connection time |

## API Key Provisioning

API keys are provisioned manually by Modulus Labs. To request API credentials:

<Steps>
  <Step title="Contact support">
    Email [support@moduluslabs.io](mailto:support@moduluslabs.io) with your integration details.
  </Step>

  <Step title="Provide information">
    Include your organization name and intended use case (desktop POS, terminal integration, etc.).
  </Step>

  <Step title="Receive credentials">
    You'll receive an API key and API secret pair. Store these securely.
  </Step>
</Steps>

<Warning>
  Never expose your API key or secret in client-side code, version control, or logs. Store them in environment variables or a secure secrets manager.
</Warning>

***

## HTTP API Authentication

HTTP requests require HMAC-SHA256 signature authentication. Each request must include three authentication headers.

### Required Headers

| Header        | Description                                                  |
| ------------- | ------------------------------------------------------------ |
| `x-api-key`   | Your API key                                                 |
| `x-timestamp` | ISO 8601 timestamp (must be within 5 minutes of server time) |
| `x-signature` | Base64-encoded HMAC-SHA256 signature                         |

### String-to-Sign Format

Construct the string to sign using this format:

```text theme={null}
METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + SHA256(BODY)
```

Where:

* `METHOD` - HTTP method in uppercase (`GET`, `POST`)
* `PATH` - Request path (e.g., `/v1/terminals`)
* `TIMESTAMP` - Value of `x-timestamp` header
* `SHA256(BODY)` - Hex-encoded SHA256 hash of request body (empty string hash for GET requests)

### Signature Computation

```text theme={null}
signature = Base64(HMAC-SHA256(apiSecret, stringToSign))
```

### Example

For a GET request to `/v1/terminals` at `2024-01-15T10:30:00.000Z`:

```text theme={null}
StringToSign:
GET
/v1/terminals
2024-01-15T10:30:00.000Z
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

Headers:
x-api-key: your-api-key
x-timestamp: 2024-01-15T10:30:00.000Z
x-signature: <base64-encoded-signature>
```

<Note>
  `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` is the SHA256 hash of an empty string.
</Note>

### Code Examples

<CodeGroup>
  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  const API_KEY = process.env.MODULUS_API_KEY;
  const API_SECRET = process.env.MODULUS_API_SECRET;
  const BASE_URL = 'https://{your-api-endpoint}';

  /**
   * Generate authentication headers for HTTP API requests
   * @param {string} method - HTTP method (GET, POST)
   * @param {string} path - Request path (e.g., /v1/terminals)
   * @param {object|null} body - Request body (null for GET requests)
   * @returns {object} Headers object with authentication
   */
  function generateAuthHeaders(method, path, body = null) {
    const timestamp = new Date().toISOString();

    // SHA256 hash of body (empty string for GET)
    const bodyString = body ? JSON.stringify(body) : '';
    const bodyHash = crypto
      .createHash('sha256')
      .update(bodyString)
      .digest('hex');

    // Construct string to sign
    const stringToSign = `${method}\n${path}\n${timestamp}\n${bodyHash}`;

    // Compute HMAC-SHA256 signature
    const signature = crypto
      .createHmac('sha256', API_SECRET)
      .update(stringToSign)
      .digest('base64');

    return {
      'Content-Type': 'application/json',
      'x-api-key': API_KEY,
      'x-timestamp': timestamp,
      'x-signature': signature
    };
  }

  // Example: GET request
  async function getTerminals() {
    const path = '/v1/terminals';
    const headers = generateAuthHeaders('GET', path);

    const response = await fetch(`${BASE_URL}${path}`, {
      method: 'GET',
      headers
    });

    return response.json();
  }

  // Example: POST request
  async function createPayment(terminalId, paymentData) {
    const path = `/v1/terminals/${terminalId}/payments`;
    const headers = generateAuthHeaders('POST', path, paymentData);

    const response = await fetch(`${BASE_URL}${path}`, {
      method: 'POST',
      headers,
      body: JSON.stringify(paymentData)
    });

    return response.json();
  }
  ```

  ```python Python theme={null}
  import hashlib
  import hmac
  import base64
  import json
  import os
  from datetime import datetime, timezone
  import requests

  API_KEY = os.getenv('MODULUS_API_KEY')
  API_SECRET = os.getenv('MODULUS_API_SECRET')
  BASE_URL = 'https://{your-api-endpoint}'


  def generate_auth_headers(method: str, path: str, body: dict = None) -> dict:
      """
      Generate authentication headers for HTTP API requests.

      Args:
          method: HTTP method (GET, POST)
          path: Request path (e.g., /v1/terminals)
          body: Request body (None for GET requests)

      Returns:
          Headers dict with authentication
      """
      timestamp = datetime.now(timezone.utc).isoformat(timespec='milliseconds').replace('+00:00', 'Z')

      # SHA256 hash of body (empty string for GET)
      body_string = json.dumps(body) if body else ''
      body_hash = hashlib.sha256(body_string.encode()).hexdigest()

      # Construct string to sign
      string_to_sign = f'{method}\n{path}\n{timestamp}\n{body_hash}'

      # Compute HMAC-SHA256 signature
      signature = base64.b64encode(
          hmac.new(
              API_SECRET.encode(),
              string_to_sign.encode(),
              hashlib.sha256
          ).digest()
      ).decode()

      return {
          'Content-Type': 'application/json',
          'x-api-key': API_KEY,
          'x-timestamp': timestamp,
          'x-signature': signature
      }


  # Example: GET request
  def get_terminals():
      path = '/v1/terminals'
      headers = generate_auth_headers('GET', path)

      response = requests.get(f'{BASE_URL}{path}', headers=headers)
      return response.json()


  # Example: POST request
  def create_payment(terminal_id: str, payment_data: dict):
      path = f'/v1/terminals/{terminal_id}/payments'
      headers = generate_auth_headers('POST', path, payment_data)

      response = requests.post(
          f'{BASE_URL}{path}',
          headers=headers,
          json=payment_data
      )
      return response.json()
  ```
</CodeGroup>

### Common Authentication Errors

| Error Code          | HTTP Status | Description                               | Solution                            |
| ------------------- | ----------- | ----------------------------------------- | ----------------------------------- |
| `UNAUTHORIZED`      | 401         | Missing or invalid API key                | Verify your API key is correct      |
| `INVALID_SIGNATURE` | 401         | HMAC signature verification failed        | Check signature computation         |
| `TIMESTAMP_EXPIRED` | 401         | Request timestamp outside 5-minute window | Ensure system clock is synchronized |

***

## WebSocket API Authentication

WebSocket connections authenticate during the connection handshake. Provide your API key via headers or query parameters.

### Authentication via Headers

Include the `x-api-key` header in your WebSocket connection request:

| Header       | Required | Description                 |
| ------------ | -------- | --------------------------- |
| `x-api-key`  | Yes      | Your API key                |
| `x-group-id` | No       | Group identifier (optional) |

### Authentication via Query Parameter

Alternatively, pass your API key as a query parameter:

```text theme={null}
wss://{your-api-endpoint}/v1?x-api-key=your-api-key
```

<Warning>
  When using query parameters, your API key may appear in server logs. Use header-based authentication in production when possible.
</Warning>

### Code Examples

<CodeGroup>
  ```javascript Node.js theme={null}
  const WebSocket = require('ws');

  const API_KEY = process.env.MODULUS_API_KEY;
  const WS_ENDPOINT = 'wss://{your-api-endpoint}/v1';

  // Option 1: Header-based authentication (recommended)
  function connectWithHeaders() {
    const ws = new WebSocket(WS_ENDPOINT, {
      headers: {
        'x-api-key': API_KEY
      }
    });

    ws.on('open', () => {
      console.log('Connected with header authentication');
    });

    ws.on('error', (error) => {
      console.error('Connection error:', error.message);
    });

    return ws;
  }

  // Option 2: Query parameter authentication
  function connectWithQueryParam() {
    const ws = new WebSocket(`${WS_ENDPOINT}?x-api-key=${API_KEY}`);

    ws.on('open', () => {
      console.log('Connected with query parameter authentication');
    });

    return ws;
  }
  ```

  ```python Python theme={null}
  import asyncio
  import websockets
  import os

  API_KEY = os.getenv('MODULUS_API_KEY')
  WS_ENDPOINT = 'wss://{your-api-endpoint}/v1'


  # Option 1: Header-based authentication (recommended)
  async def connect_with_headers():
      headers = {
          'x-api-key': API_KEY
      }

      async with websockets.connect(WS_ENDPOINT, extra_headers=headers) as ws:
          print('Connected with header authentication')
          # Handle messages...
          async for message in ws:
              print(f'Received: {message}')


  # Option 2: Query parameter authentication
  async def connect_with_query_param():
      url = f'{WS_ENDPOINT}?x-api-key={API_KEY}'

      async with websockets.connect(url) as ws:
          print('Connected with query parameter authentication')
          # Handle messages...
          async for message in ws:
              print(f'Received: {message}')
  ```
</CodeGroup>

***

## Security Best Practices

<CardGroup cols={2}>
  <Card title="Protect Credentials" icon="key">
    Store API keys and secrets in environment variables or a secure secrets manager. Never hardcode them in source code.
  </Card>

  <Card title="Use Headers for WebSocket" icon="shield">
    Prefer header-based authentication over query parameters to avoid keys appearing in logs and browser history.
  </Card>

  <Card title="Synchronize Time" icon="clock">
    For HTTP API, ensure your system clock is synchronized with NTP. Timestamps must be within 5 minutes of server time.
  </Card>

  <Card title="Rotate Keys" icon="rotate">
    Rotate API keys periodically and immediately if you suspect compromise. Contact support for key rotation.
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="HTTP Quickstart" icon="rocket" href="./http/quickstart">
    Start integrating with the HTTP API
  </Card>

  <Card title="WebSocket Quickstart" icon="rocket" href="./websocket/quickstart">
    Start integrating with the WebSocket API
  </Card>

  <Card title="Core Concepts" icon="lightbulb" href="./concepts">
    Learn about device enforcement and reconnection resilience
  </Card>

  <Card title="Data Types" icon="brackets-curly" href="./reference">
    Review shared data types and error codes
  </Card>
</CardGroup>
