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

# WebSocket Quickstart

> Establish and maintain WebSocket connections with authentication, heartbeat, and reconnection handling

## Overview

This guide covers how to establish a WebSocket connection, authenticate your POS client, and maintain a healthy connection with proper heartbeat and reconnection handling.

<Steps>
  <Step title="Authenticate">
    Connect with your API key via headers or query parameters
  </Step>

  <Step title="Establish Connection">
    Open WebSocket connection to the API endpoint
  </Step>

  <Step title="Implement Heartbeat">
    Send periodic ping messages to maintain connection health
  </Step>

  <Step title="Handle Messages">
    Process responses and push notifications
  </Step>
</Steps>

## Prerequisites

Before connecting, ensure you have:

<CardGroup cols={2}>
  <Card title="API Credentials" icon="key">
    API key provided by Modulus Labs for your POS integration
  </Card>

  <Card title="WebSocket Client" icon="plug">
    A WebSocket client library for your programming language
  </Card>

  <Card title="Network Access" icon="globe">
    Outbound access to the WebSocket endpoint over port 443
  </Card>

  <Card title="JSON Parser" icon="brackets-curly">
    Ability to serialize and parse JSON messages
  </Card>
</CardGroup>

## WebSocket Endpoint

Connect to the WebSocket endpoint:

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

<Note>
  Replace `{your-api-endpoint}` with your provisioned API endpoint. Contact [support@moduluslabs.io](mailto:support@moduluslabs.io) to obtain your endpoint URL and credentials.
</Note>

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

## Establishing a Connection

<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';

  function connect() {
    const ws = new WebSocket(WS_ENDPOINT, {
      headers: {
        'x-api-key': API_KEY
      }
    });

    ws.on('open', () => {
      console.log('Connected to WebSocket API');
      startHeartbeat(ws);
    });

    ws.on('message', (data) => {
      const message = JSON.parse(data.toString());
      handleMessage(message);
    });

    ws.on('close', (code, reason) => {
      console.log(`Connection closed: ${code} - ${reason}`);
      // Implement reconnection logic
      setTimeout(() => connect(), 5000);
    });

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

    return ws;
  }

  function handleMessage(message) {
    switch (message.action) {
      case 'terminalsResponse':
        console.log('Terminals:', message.terminals);
        break;
      case 'paymentComplete':
        console.log('Payment complete:', message.paymentResponse);
        break;
      case 'terminalStatusUpdate':
        console.log('Terminal status:', message.status);
        break;
      case 'forceDisconnect':
        console.log('Disconnected:', message.reason);
        break;
      case 'pong':
        console.log('Heartbeat received');
        break;
      default:
        console.log('Unknown message:', message);
    }
  }

  const ws = connect();
  ```

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

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

  async def connect():
      headers = {
          'x-api-key': API_KEY
      }

      async with websockets.connect(WS_ENDPOINT, extra_headers=headers) as ws:
          print('Connected to WebSocket API')

          # Start heartbeat task
          heartbeat_task = asyncio.create_task(heartbeat(ws))

          try:
              async for message in ws:
                  data = json.loads(message)
                  await handle_message(data)
          except websockets.ConnectionClosed as e:
              print(f'Connection closed: {e.code} - {e.reason}')
          finally:
              heartbeat_task.cancel()

  async def handle_message(message):
      action = message.get('action')

      if action == 'terminalsResponse':
          print(f"Terminals: {message['terminals']}")
      elif action == 'paymentComplete':
          print(f"Payment complete: {message['paymentResponse']}")
      elif action == 'terminalStatusUpdate':
          print(f"Terminal status: {message['status']}")
      elif action == 'forceDisconnect':
          print(f"Disconnected: {message['reason']}")
      elif action == 'pong':
          print('Heartbeat received')
      else:
          print(f'Unknown message: {message}')

  async def heartbeat(ws):
      while True:
          await asyncio.sleep(30)
          await ws.send(json.dumps({'action': 'ping'}))

  async def main():
      while True:
          try:
              await connect()
          except Exception as e:
              print(f'Connection error: {e}')
          print('Reconnecting in 5 seconds...')
          await asyncio.sleep(5)

  asyncio.run(main())
  ```
</CodeGroup>

## Quickstart Flow

<Steps>
  <Step title="Connect to WebSocket Endpoint">
    Establish a WebSocket connection with your API key:

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

  <Step title="Discover Available Terminals">
    Request a list of connected terminals:

    ```json theme={null}
    { "action": "getTerminals" }
    ```

    Receive terminal list:

    ```json theme={null}
    {
      "action": "terminalsResponse",
      "terminals": [
        {
          "deviceId": "TERM-001",
          "status": "online"
        }
      ]
    }
    ```
  </Step>

  <Step title="Initiate Payment">
    Send a payment request to a terminal:

    ```json theme={null}
    {
      "action": "payment",
      "terminalId": "TERM-001",
      "paymentRequest": {
        "transactionId": "TXN-20240115-001",
        "amount": "99.99",
        "currency": "USD",
        "paymentMethod": "CARD"
      }
    }
    ```
  </Step>

  <Step title="Receive Payment Result">
    The terminal processes the payment and returns the result via push notification:

    ```json theme={null}
    {
      "action": "paymentComplete",
      "terminalId": "TERM-001",
      "paymentResponse": {
        "transactionId": "TXN-20240115-001",
        "status": "SUCCESS",
        "amount": "99.99",
        "authorizationCode": "AUTH123456"
      }
    }
    ```
  </Step>
</Steps>

## Heartbeat / Keep-Alive

Maintain connection health with periodic ping/pong messages to prevent timeouts and detect stale connections.

### Recommended Settings

| Setting              | Value      |
| -------------------- | ---------- |
| Ping interval        | 30 seconds |
| Pong timeout         | 10 seconds |
| Reconnect on failure | Yes        |

### Implementation

<CodeGroup>
  ```javascript Node.js theme={null}
  let pingTimeout;
  let heartbeatInterval;

  function startHeartbeat(ws) {
    heartbeatInterval = setInterval(() => {
      ws.send(JSON.stringify({ action: 'ping' }));

      // Set timeout for pong response
      pingTimeout = setTimeout(() => {
        console.log('Pong timeout - connection may be dead');
        ws.terminate();
      }, 10000);
    }, 30000);
  }

  function handleMessage(message) {
    if (message.action === 'pong') {
      // Clear timeout when pong received
      clearTimeout(pingTimeout);
      console.log('Heartbeat OK');
    }
  }

  function stopHeartbeat() {
    clearInterval(heartbeatInterval);
    clearTimeout(pingTimeout);
  }
  ```

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

  ping_timeout_task = None

  async def heartbeat(ws):
      global ping_timeout_task

      while True:
          await asyncio.sleep(30)
          await ws.send(json.dumps({'action': 'ping'}))

          # Set timeout for pong response
          ping_timeout_task = asyncio.create_task(pong_timeout(ws))

  async def pong_timeout(ws):
      await asyncio.sleep(10)
      print('Pong timeout - connection may be dead')
      await ws.close()

  async def handle_message(message):
      global ping_timeout_task

      if message.get('action') == 'pong':
          # Cancel timeout when pong received
          if ping_timeout_task:
              ping_timeout_task.cancel()
          print('Heartbeat OK')
  ```
</CodeGroup>

## Testing Your Connection

<Steps>
  <Step title="Connect to the WebSocket endpoint">
    Verify successful connection by checking for the `open` event
  </Step>

  <Step title="Send getTerminals action">
    Verify you receive a `terminalsResponse` with available terminals
  </Step>

  <Step title="Test heartbeat">
    Send a `ping` and verify you receive a `pong` response
  </Step>

  <Step title="Test reconnection">
    Simulate a connection drop and verify automatic reconnection works
  </Step>
</Steps>

### Quick Connection Test

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

  async function testConnection() {
    const ws = new WebSocket('wss://{your-api-endpoint}/v1', {
      headers: { 'x-api-key': process.env.MODULUS_API_KEY }
    });

    ws.on('open', () => {
      console.log('1. Connection established');

      // Test getTerminals
      ws.send(JSON.stringify({ action: 'getTerminals' }));

      // Test ping
      setTimeout(() => {
        ws.send(JSON.stringify({ action: 'ping' }));
      }, 1000);
    });

    ws.on('message', (data) => {
      const message = JSON.parse(data.toString());

      if (message.action === 'terminalsResponse') {
        console.log('2. Terminals received:', message.terminals.length);
      }

      if (message.action === 'pong') {
        console.log('3. Heartbeat working');
        console.log('All tests passed!');
        ws.close();
      }
    });

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

  testConnection();
  ```

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

  async def test_connection():
      headers = {'x-api-key': os.getenv('MODULUS_API_KEY')}

      async with websockets.connect(
          'wss://{your-api-endpoint}/v1',
          extra_headers=headers
      ) as ws:
          print('1. Connection established')

          # Test getTerminals
          await ws.send(json.dumps({'action': 'getTerminals'}))
          response = json.loads(await ws.recv())

          if response.get('action') == 'terminalsResponse':
              print(f"2. Terminals received: {len(response['terminals'])}")

          # Test ping
          await ws.send(json.dumps({'action': 'ping'}))
          response = json.loads(await ws.recv())

          if response.get('action') == 'pong':
              print('3. Heartbeat working')
              print('All tests passed!')

  asyncio.run(test_connection())
  ```
</CodeGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Connection Rejected">
    **Possible causes:**

    * Invalid or missing API key
    * API key not authorized for WebSocket access
    * Network firewall blocking WebSocket connections

    **Solutions:**

    * Verify your API key is correct
    * Contact [support@moduluslabs.io](mailto:support@moduluslabs.io) to verify API key permissions
    * Check that outbound WebSocket connections are allowed on port 443
  </Accordion>

  <Accordion title="Connection Drops Frequently">
    **Possible causes:**

    * Heartbeat not implemented
    * Network instability
    * Firewall or proxy terminating idle connections

    **Solutions:**

    * Implement heartbeat with 30-second ping interval
    * Check network connectivity
    * Configure proxy/firewall to allow persistent WebSocket connections
  </Accordion>

  <Accordion title="forceDisconnect Messages">
    **Possible causes:**

    * Multiple POS instances using the same API key
    * Previous connection not properly closed

    **Solutions:**

    * Ensure only one POS instance uses each API key
    * Request additional API keys for multiple terminals
    * Implement proper connection cleanup on application exit
  </Accordion>

  <Accordion title="Messages Not Received">
    **Possible causes:**

    * Message handler not processing all action types
    * JSON parsing errors
    * Connection silently dropped

    **Solutions:**

    * Log all incoming messages for debugging
    * Add try/catch around JSON parsing
    * Implement heartbeat to detect stale connections
  </Accordion>
</AccordionGroup>

## Security Best Practices

<CardGroup cols={2}>
  <Card title="Protect API Keys" icon="key">
    Store API keys in environment variables, not in source code. Never expose keys in client-side JavaScript.
  </Card>

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

  <Card title="Validate Messages" icon="check">
    Always validate incoming message structure before processing to prevent errors from malformed data.
  </Card>

  <Card title="Secure Reconnection" icon="rotate">
    When reconnecting, ensure you're connecting to the legitimate endpoint. Validate SSL certificates.
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Actions Reference" icon="paper-plane" href="./actions">
    Learn how to send commands to terminals
  </Card>

  <Card title="Push Notifications" icon="bell" href="./notifications">
    Handle real-time terminal and payment updates
  </Card>

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

  <Card title="Data Types" icon="brackets-curly" href="../reference">
    Complete schema reference for all data types
  </Card>
</CardGroup>
