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

# Core Concepts

> Single device enforcement, terminal reconnection resilience, and desktop POS recovery in the Terminal Gateway

## Overview

The Terminal Gateway implements four key concepts that apply to both HTTP and WebSocket protocols:

* **Terminal ID Resolution** - How the system identifies terminals using deviceId or connectionId
* **Single Device Enforcement** - One active connection per API key
* **Terminal Reconnection Resilience** - 60-second grace period for terminal reconnections during payments
* **Desktop POS Recovery** - Automatic and manual recovery mechanisms for missed payment results

***

## Terminal ID Resolution

When sending commands to terminals (payments, status checks, etc.), the `terminalId` parameter is resolved in the following order:

1. **Device ID (Recommended)** - The system first attempts to resolve `terminalId` as a `deviceId` from the terminal registry. This is the preferred approach as `deviceId` remains constant even when a terminal reconnects and receives a new `connectionId`.

2. **Connection ID (Legacy Fallback)** - If no matching `deviceId` is found, the system treats `terminalId` as a raw `connectionId` for backward compatibility with older integrations.

<Tip>
  Always use `deviceId` when available. It provides a stable reference that doesn't change when terminals reconnect, and enables [Terminal Reconnection Resilience](#terminal-reconnection-resilience) for handling mid-payment disconnections.
</Tip>

***

## Single Device Enforcement

The Terminal Gateway enforces single device connection per API key. Only one active connection is allowed at any time.

### How It Works

<Steps>
  <Step title="First connection established">
    Your POS client connects with an API key and becomes the active connection.
  </Step>

  <Step title="Second connection attempts">
    If another device connects with the same API key, the **existing connection is forcibly disconnected**.
  </Step>

  <Step title="Displacement notification">
    The displaced connection receives a `forceDisconnect` message (WebSocket) or loses session state (HTTP).
  </Step>

  <Step title="New connection active">
    The new connection becomes the active connection for that API key.
  </Step>
</Steps>

### Why Single Device Enforcement?

<CardGroup cols={2}>
  <Card title="Security" icon="shield">
    Prevents unauthorized access from multiple locations simultaneously.
  </Card>

  <Card title="Consistency" icon="check">
    Ensures terminal state and payment flows are managed by a single source.
  </Card>

  <Card title="Auditability" icon="file-lines">
    Clear connection tracking for compliance and debugging.
  </Card>

  <Card title="Resource Management" icon="server">
    Prevents resource exhaustion from duplicate connections.
  </Card>
</CardGroup>

### forceDisconnect Message (WebSocket)

When your WebSocket connection is displaced, you receive:

```json theme={null}
{
  "action": "forceDisconnect",
  "reason": "api_key_used_elsewhere",
  "message": "Connection displaced by new connection with same API key",
  "reconnectAllowed": true
}
```

### Disconnect Reasons

| Reason                     | Description                                    |
| -------------------------- | ---------------------------------------------- |
| `api_key_used_elsewhere`   | Another device connected with the same API key |
| `stale_connection_timeout` | Connection was inactive for too long           |
| `admin_action`             | Connection terminated by administrator         |
| `other`                    | Other system-initiated disconnect              |

### Handling forceDisconnect

<CodeGroup>
  ```javascript Node.js theme={null}
  function handleMessage(message) {
    if (message.action === 'forceDisconnect') {
      console.log('Connection displaced:', message.reason);

      // Notify user that connection was lost
      showAlert('Connection lost - another device connected with your credentials');

      // Optionally attempt reconnection if allowed
      if (message.reconnectAllowed) {
        console.log('Reconnection allowed, will attempt to reconnect...');
        setTimeout(() => connect(), 5000);
      }
    }
  }
  ```

  ```python Python theme={null}
  async def handle_message(message):
      if message.get('action') == 'forceDisconnect':
          print(f"Connection displaced: {message['reason']}")

          # Notify user that connection was lost
          show_alert('Connection lost - another device connected with your credentials')

          # Optionally attempt reconnection if allowed
          if message.get('reconnectAllowed'):
              print('Reconnection allowed, will attempt to reconnect...')
  ```
</CodeGroup>

### Best Practices

* **Use separate API keys** for each POS terminal or workstation
* **Do not** run multiple instances of your application with the same API key
* **Implement graceful handling** of `forceDisconnect` with user notification
* **Log displacement events** for debugging connection issues

***

## Terminal Reconnection Resilience

Terminals with a stable device identifier (`deviceId`) benefit from reconnection resilience during payment processing. If a terminal disconnects mid-payment, there's a 60-second grace period for it to reconnect.

### How It Works

<Steps>
  <Step title="Terminal connects with deviceId">
    Terminal connects with the `X-Device-Id` header and is registered in the terminal registry.
  </Step>

  <Step title="Payment initiated">
    Your POS sends a payment request to the terminal.
  </Step>

  <Step title="Terminal disconnects">
    If the terminal loses connection during payment processing, the transaction status changes to `AWAITING_RECONNECT`.
  </Step>

  <Step title="60-second grace period">
    The terminal has 60 seconds to reconnect and deliver the payment result.
  </Step>

  <Step title="Terminal reconnects (within grace period)">
    Cached payment results are processed and delivered to your POS.
  </Step>

  <Step title="Grace period expires">
    If the terminal reconnects after 60 seconds with a successful payment, the payment is automatically voided.
  </Step>
</Steps>

### Status Flow Diagram

```text theme={null}
Payment Initiated
       │
       ▼
   PENDING ───────────────────────────────┐
       │                                  │
       │ Terminal disconnects             │ Terminal responds
       ▼                                  ▼
AWAITING_RECONNECT                   COMPLETED / FAILED
       │
       ├─── Terminal reconnects within 60s ───▶ COMPLETED / FAILED
       │
       └─── Grace period expires + successful payment ───▶ VOIDED
```

### Terminal Status Values

Your POS receives `terminalStatusUpdate` notifications (WebSocket) or can poll the terminals endpoint (HTTP):

| Status                     | Description                                            |
| -------------------------- | ------------------------------------------------------ |
| `connected` / `online`     | Terminal is online and ready                           |
| `reconnecting`             | Terminal disconnected, may reconnect within 60 seconds |
| `disconnected` / `offline` | Terminal is fully offline                              |

### Voided Payments

If a terminal reconnects **after** the 60-second grace period with a successful payment result:

1. The payment is automatically voided to prevent charging the customer without your knowledge
2. Your POS receives a `paymentVoided` notification (WebSocket)
3. Transaction status is set to `VOIDED`

### paymentVoided Notification

```json theme={null}
{
  "action": "paymentVoided",
  "terminalId": "TERM-001",
  "transactionId": "TXN-20240115-001",
  "reason": "Terminal reconnected after grace period expired",
  "originalPaymentResponse": {
    "transactionId": "TXN-20240115-001",
    "status": "SUCCESS",
    "amount": "99.99",
    "currency": "USD"
  },
  "timestamp": "2024-01-15T10:38:30.000Z"
}
```

### Handling Voided Payments

<Warning>
  Always handle `paymentVoided` notifications to inform staff that a late payment was automatically reversed. This prevents confusion when the terminal shows "approved" but no payment was collected.
</Warning>

<CodeGroup>
  ```javascript Node.js theme={null}
  function handlePaymentVoided(message) {
    const { transactionId, reason, originalPaymentResponse } = message;

    console.log(`Payment ${transactionId} was automatically voided`);
    console.log(`Reason: ${reason}`);
    console.log(`Original amount: ${originalPaymentResponse.amount}`);

    // Update order to require new payment
    updateOrderStatus(transactionId, 'VOIDED');

    // Alert staff with clear messaging
    showAlert(
      `Payment for ${originalPaymentResponse.amount} ${originalPaymentResponse.currency} ` +
      `was voided: Terminal reconnected too late. Please retry the payment.`
    );

    // Log for audit trail
    logVoidedPayment({
      transactionId,
      reason,
      originalAmount: originalPaymentResponse.amount,
      voidedAt: message.timestamp
    });
  }
  ```

  ```python Python theme={null}
  async def handle_payment_voided(message):
      transaction_id = message['transactionId']
      reason = message['reason']
      original_response = message['originalPaymentResponse']

      print(f"Payment {transaction_id} was automatically voided")
      print(f"Reason: {reason}")
      print(f"Original amount: {original_response['amount']}")

      # Update order to require new payment
      await update_order_status(transaction_id, 'VOIDED')

      # Alert staff with clear messaging
      show_alert(
          f"Payment for {original_response['amount']} {original_response['currency']} "
          f"was voided: Terminal reconnected too late. Please retry the payment."
      )

      # Log for audit trail
      log_voided_payment({
          'transactionId': transaction_id,
          'reason': reason,
          'originalAmount': original_response['amount'],
          'voidedAt': message['timestamp']
      })
  ```
</CodeGroup>

### Best Practices

<CardGroup cols={2}>
  <Card title="Use deviceId" icon="id-card">
    Always configure terminals with a stable `deviceId` via the `X-Device-Id` header to benefit from reconnection resilience.
  </Card>

  <Card title="Handle All Statuses" icon="list-check">
    Implement handlers for `AWAITING_RECONNECT` and `VOIDED` statuses in your payment flow.
  </Card>

  <Card title="Monitor Connectivity" icon="wifi">
    Display terminal connection status in your POS UI to help staff identify issues.
  </Card>

  <Card title="Audit Voided Payments" icon="file-lines">
    Log all voided payments for reconciliation and customer service purposes.
  </Card>
</CardGroup>

***

## Desktop POS Recovery

When a desktop POS disconnects after initiating a payment and misses the successful result, the Terminal Gateway provides two complementary recovery mechanisms to ensure payment results are never lost.

### Recovery Mechanisms

<CardGroup cols={2}>
  <Card title="Undelivered Message Queue" icon="inbox">
    **Automatic recovery** for brief network interruptions. Payment results are stored and pushed automatically upon reconnection.
  </Card>

  <Card title="Transaction Status Query API" icon="magnifying-glass">
    **Manual fallback** for longer disconnections. Query transaction status at any time after reconnection.
  </Card>
</CardGroup>

### Undelivered Message Queue

When a payment completes but the desktop POS cannot receive the result (due to disconnection or network issues), the Terminal Gateway automatically queues the message for later delivery.

<Steps>
  <Step title="Payment completes">
    The terminal processes the payment and sends the result.
  </Step>

  <Step title="Delivery fails">
    The gateway detects that the desktop POS is disconnected or unreachable.
  </Step>

  <Step title="Message queued">
    The payment result is stored in the undelivered message queue.
  </Step>

  <Step title="Desktop reconnects">
    Upon reconnection, queued messages are automatically pushed to the desktop POS.
  </Step>
</Steps>

<Note>
  The message queue handles automatic recovery for common network blips without requiring any action from the desktop POS. Your application receives the `paymentComplete` notification as if the connection had never been interrupted.
</Note>

### Transaction Status Query API

For longer disconnections or edge cases where the message queue may not apply, use the Transaction Status Query API to manually check payment status.

**HTTP API:**

```text theme={null}
GET /v1/transactions/{transactionId}
```

**WebSocket API:**

```json theme={null}
{
  "action": "getTransactionStatus",
  "transactionId": "TXN-20240115-001"
}
```

<CodeGroup>
  ```javascript Node.js theme={null}
  const BASE_URL = 'https://{your-api-endpoint}';

  // Query transaction status after reconnection
  // See Authentication docs for generateAuthHeaders implementation
  async function checkTransactionStatus(transactionId) {
    const path = `/v1/transactions/${transactionId}`;
    const headers = generateAuthHeaders('GET', path);

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

    const transaction = await response.json();

    if (transaction.status === 'COMPLETED') {
      console.log(`Payment was successful: ${transactionId}`);
      console.log(`Amount: ${transaction.amount} ${transaction.currency}`);
      // Update your order accordingly
      updateOrderStatus(transactionId, 'PAID');
    } else if (transaction.status === 'FAILED') {
      console.log(`Payment failed: ${transaction.errorMessage}`);
      // Prompt for retry
      promptPaymentRetry(transactionId);
    } else if (transaction.status === 'PENDING') {
      console.log('Payment still processing...');
      // Check again later
    }

    return transaction;
  }

  // On reconnection, check any pending transactions
  async function onReconnect(pendingTransactionIds) {
    for (const txnId of pendingTransactionIds) {
      await checkTransactionStatus(txnId);
    }
  }
  ```

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

  BASE_URL = 'https://{your-api-endpoint}'

  # Query transaction status after reconnection
  # See Authentication docs for generate_auth_headers implementation
  async def check_transaction_status(transaction_id: str):
      path = f'/v1/transactions/{transaction_id}'
      headers = generate_auth_headers('GET', path)

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

      if transaction['status'] == 'COMPLETED':
          print(f"Payment was successful: {transaction_id}")
          print(f"Amount: {transaction['amount']} {transaction['currency']}")
          # Update your order accordingly
          await update_order_status(transaction_id, 'PAID')
      elif transaction['status'] == 'FAILED':
          print(f"Payment failed: {transaction.get('errorMessage')}")
          # Prompt for retry
          prompt_payment_retry(transaction_id)
      elif transaction['status'] == 'PENDING':
          print('Payment still processing...')
          # Check again later

      return transaction


  # On reconnection, check any pending transactions
  async def on_reconnect(pending_transaction_ids: list):
      for txn_id in pending_transaction_ids:
          await check_transaction_status(txn_id)
  ```
</CodeGroup>

### When to Use Each Mechanism

| Scenario                                | Recommended Approach                                     |
| --------------------------------------- | -------------------------------------------------------- |
| Brief network interruption (seconds)    | Automatic - Message queue handles delivery               |
| Desktop restart during payment          | Query API on startup with pending transaction IDs        |
| Extended offline period (minutes/hours) | Query API for all transactions initiated during downtime |
| Uncertain if result was received        | Query API to verify transaction status                   |

### Best Practices

<CardGroup cols={2}>
  <Card title="Track Pending Transactions" icon="list">
    Maintain a local list of initiated transaction IDs until you receive confirmation. Check these on reconnection.
  </Card>

  <Card title="Implement Idempotent Handlers" icon="shield-check">
    Your payment handlers should safely handle receiving the same result twice (from queue and query).
  </Card>

  <Card title="Use Transaction IDs" icon="fingerprint">
    Always use unique, meaningful transaction IDs that you can query later if needed.
  </Card>

  <Card title="Log Recovery Events" icon="file-lines">
    Log when payments are recovered via queue or query for debugging and audit purposes.
  </Card>
</CardGroup>

<Tip>
  Together, these mechanisms ensure complete coverage: the message queue handles automatic recovery for common network blips, while the query API serves as a manual fallback for longer disconnections or edge cases.
</Tip>

## Next Steps

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="./authentication">
    Learn about API authentication methods
  </Card>

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

  <Card title="HTTP Quickstart" icon="server" href="./http/quickstart">
    Get started with the HTTP API
  </Card>

  <Card title="WebSocket Quickstart" icon="bolt" href="./websocket/quickstart">
    Get started with the WebSocket API
  </Card>
</CardGroup>
