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

# Actions Reference

> Send commands to discover terminals and initiate payments

## Overview

Actions are commands you send to the WebSocket API to interact with terminals. Each action has a specific request format and returns a corresponding response.

| Action         | Description                            |
| -------------- | -------------------------------------- |
| `getTerminals` | Retrieve a list of available terminals |
| `payment`      | Initiate a payment request             |
| `ping`         | Keep-alive heartbeat                   |

## getTerminals

Request a list of available terminals in your group.

### Request

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

### Response

```json theme={null}
{
  "action": "terminalsResponse",
  "terminals": [
    {
      "connectionId": "abc123xyz",
      "deviceId": "TERM-001",
      "connectedAt": "2024-01-15T10:30:00.000Z",
      "lastActivity": "2024-01-15T10:35:00.000Z",
      "status": "online",
      "metadata": {}
    }
  ],
  "timestamp": "2024-01-15T10:35:30.000Z"
}
```

### Response Fields

| Field                      | Type   | Description                              |
| -------------------------- | ------ | ---------------------------------------- |
| `terminals`                | array  | List of available terminals              |
| `terminals[].connectionId` | string | Unique connection identifier             |
| `terminals[].deviceId`     | string | Stable device identifier (if configured) |
| `terminals[].connectedAt`  | string | ISO 8601 timestamp of connection         |
| `terminals[].lastActivity` | string | ISO 8601 timestamp of last activity      |
| `terminals[].status`       | string | `online`, `offline`, or `reconnecting`   |
| `terminals[].metadata`     | object | Custom terminal metadata                 |
| `timestamp`                | string | ISO 8601 timestamp of the response       |

### Code Examples

<CodeGroup>
  ```javascript Node.js theme={null}
  function getTerminals(ws) {
    ws.send(JSON.stringify({ action: 'getTerminals' }));
  }

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

    if (message.action === 'terminalsResponse') {
      const terminals = message.terminals;

      console.log(`Found ${terminals.length} terminal(s)`);

      terminals.forEach((terminal) => {
        console.log(`- ${terminal.deviceId}: ${terminal.status}`);
      });

      // Store terminals for payment operations
      availableTerminals = terminals;
    }
  });
  ```

  ```python Python theme={null}
  async def get_terminals(ws):
      await ws.send(json.dumps({'action': 'getTerminals'}))

  async def handle_message(message):
      if message.get('action') == 'terminalsResponse':
          terminals = message['terminals']

          print(f"Found {len(terminals)} terminal(s)")

          for terminal in terminals:
              print(f"- {terminal['deviceId']}: {terminal['status']}")

          # Store terminals for payment operations
          global available_terminals
          available_terminals = terminals
  ```
</CodeGroup>

***

## payment

Initiate a payment request to a terminal. The terminal processes the payment and returns the result via a `paymentComplete` push notification.

### Request

```json theme={null}
{
  "action": "payment",
  "terminalId": "TERM-001",
  "paymentRequest": {
    "transactionId": "TXN-20240115-001",
    "amount": "99.99",
    "currency": "USD",
    "paymentMethod": "CARD",
    "products": [
      {
        "id": "PROD-001",
        "name": "Widget",
        "price": "99.99",
        "quantity": 1
      }
    ],
    "customerInfo": {
      "customerId": "CUST-123",
      "email": "customer@example.com"
    },
    "metadata": {
      "orderId": "ORD-12345"
    }
  }
}
```

### Parameters

<ParamField body="action" type="string" required>
  Must be `"payment"`
</ParamField>

<ParamField body="terminalId" type="string" required>
  The terminal identifier to send the payment to. Resolved as `deviceId` first, falling back to `connectionId`. See [Terminal ID Resolution](../concepts#terminal-id-resolution).
</ParamField>

<ParamField body="paymentRequest" type="object" required>
  The payment details

  <Expandable title="properties">
    <ParamField body="transactionId" type="string" required>
      Unique transaction identifier. Use this to track and reconcile payments.
    </ParamField>

    <ParamField body="amount" type="string | number" required>
      Payment amount. Can be a string (recommended for precision) or number.
    </ParamField>

    <ParamField body="currency" type="string" required>
      ISO 4217 currency code (e.g., `"USD"`, `"EUR"`, `"PHP"`)
    </ParamField>

    <ParamField body="paymentMethod" type="string" required>
      Payment method: `"CARD"`, `"CASH"`, `"MOBILE"`, or `"OTHER"`
    </ParamField>

    <ParamField body="products" type="array">
      Array of products in the transaction. See [Product schema](../reference#product).
    </ParamField>

    <ParamField body="customerInfo" type="object">
      Customer information. See [CustomerInfo schema](../reference#customerinfo).
    </ParamField>

    <ParamField body="metadata" type="object">
      Custom key-value pairs for your use. Returned in the payment response.
    </ParamField>
  </Expandable>
</ParamField>

### Message Sent to Terminal

When you send a payment action, the terminal receives:

```json theme={null}
{
  "action": "paymentRequest",
  "desktopId": "desktop-connection-id",
  "paymentRequest": {
    "transactionId": "TXN-20240115-001",
    "amount": "99.99",
    "currency": "USD",
    "paymentMethod": "CARD",
    "products": [...],
    "customerInfo": {...},
    "metadata": {...}
  },
  "timestamp": "2024-01-15T10:37:00.000Z"
}
```

### Response

The payment result is delivered via a [`paymentComplete`](./notifications#paymentcomplete) push notification when the terminal finishes processing.

### Code Examples

<CodeGroup>
  ```javascript Node.js theme={null}
  function initiatePayment(ws, terminalId, paymentData) {
    const request = {
      action: 'payment',
      terminalId: terminalId,
      paymentRequest: {
        transactionId: `TXN-${Date.now()}`,
        amount: paymentData.amount,
        currency: paymentData.currency || 'USD',
        paymentMethod: 'CARD',
        products: paymentData.products || [],
        customerInfo: paymentData.customer || {},
        metadata: {
          orderId: paymentData.orderId
        }
      }
    };

    ws.send(JSON.stringify(request));
    console.log(`Payment initiated: ${request.paymentRequest.transactionId}`);

    return request.paymentRequest.transactionId;
  }

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

    if (message.action === 'paymentComplete') {
      const response = message.paymentResponse;

      if (response.status === 'SUCCESS') {
        console.log(`Payment successful: ${response.transactionId}`);
        console.log(`Authorization: ${response.authorizationCode}`);
        // Update order status, print receipt, etc.
      } else {
        console.error(`Payment failed: ${response.errorMessage}`);
        // Handle failure, notify customer
      }
    }
  });

  // Usage
  const txnId = initiatePayment(ws, 'TERM-001', {
    amount: '99.99',
    orderId: 'ORD-12345',
    products: [
      { id: 'PROD-001', name: 'Widget', price: '99.99', quantity: 1 }
    ]
  });
  ```

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

  async def initiate_payment(ws, terminal_id, payment_data):
      request = {
          'action': 'payment',
          'terminalId': terminal_id,
          'paymentRequest': {
              'transactionId': f"TXN-{int(time.time() * 1000)}",
              'amount': payment_data['amount'],
              'currency': payment_data.get('currency', 'USD'),
              'paymentMethod': 'CARD',
              'products': payment_data.get('products', []),
              'customerInfo': payment_data.get('customer', {}),
              'metadata': {
                  'orderId': payment_data.get('orderId')
              }
          }
      }

      await ws.send(json.dumps(request))
      print(f"Payment initiated: {request['paymentRequest']['transactionId']}")

      return request['paymentRequest']['transactionId']

  async def handle_message(message):
      if message.get('action') == 'paymentComplete':
          response = message['paymentResponse']

          if response['status'] == 'SUCCESS':
              print(f"Payment successful: {response['transactionId']}")
              print(f"Authorization: {response['authorizationCode']}")
              # Update order status, print receipt, etc.
          else:
              print(f"Payment failed: {response.get('errorMessage')}")
              # Handle failure, notify customer

  # Usage
  txn_id = await initiate_payment(ws, 'TERM-001', {
      'amount': '99.99',
      'orderId': 'ORD-12345',
      'products': [
          {'id': 'PROD-001', 'name': 'Widget', 'price': '99.99', 'quantity': 1}
      ]
  })
  ```
</CodeGroup>

### Error Responses

| Error       | Message                                              | Cause                  |
| ----------- | ---------------------------------------------------- | ---------------------- |
| Bad Request | terminalId is required                               | Missing terminal ID    |
| Bad Request | paymentRequest is required                           | Missing payment data   |
| Not Found   | Terminal connection not found                        | Terminal not connected |
| Forbidden   | Cannot send message to connection in different group | Group mismatch         |

***

## ping / pong

Keep-alive mechanism to maintain connection health and detect stale connections.

### Ping Request

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

### Pong Response

```json theme={null}
{
  "action": "pong",
  "timestamp": "2024-01-15T10:38:00.000Z"
}
```

### Recommended Settings

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

### Code Examples

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

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

      pingTimeout = setTimeout(() => {
        console.log('Connection timeout - reconnecting');
        ws.terminate();
      }, 10000);
    }, 30000);
  }

  function handlePong() {
    clearTimeout(pingTimeout);
  }

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

    if (message.action === 'pong') {
      handlePong();
    }
  });
  ```

  ```python Python theme={null}
  async def heartbeat(ws):
      while True:
          await asyncio.sleep(30)

          try:
              await ws.send(json.dumps({'action': 'ping'}))
              # Wait for pong with timeout
              await asyncio.wait_for(wait_for_pong(), timeout=10)
          except asyncio.TimeoutError:
              print('Connection timeout - reconnecting')
              await ws.close()
              break
  ```
</CodeGroup>

### Server-Initiated Pong

The server may also send `pong` messages to check client responsiveness. Your client should update its activity tracking when receiving these messages.

***

## Error Response Format

All error responses follow this format:

```json theme={null}
{
  "error": "Bad Request",
  "message": "terminalId is required"
}
```

### Common Errors

| Error                 | Message                                              | Cause                     |
| --------------------- | ---------------------------------------------------- | ------------------------- |
| Bad Request           | Invalid JSON in message body                         | Malformed JSON in request |
| Bad Request           | terminalId is required                               | Missing required field    |
| Bad Request           | paymentRequest is required                           | Missing payment data      |
| Not Found             | Connection not found                                 | Invalid connection ID     |
| Not Found             | Terminal connection not found                        | Terminal not connected    |
| Forbidden             | Only desktop devices can request terminal list       | Wrong device type         |
| Forbidden             | Cannot send message to connection in different group | Group mismatch            |
| Internal Server Error | Failed to process message                            | Server-side error         |

## Next Steps

<CardGroup cols={2}>
  <Card title="Push Notifications" icon="bell" href="./notifications">
    Handle payment and terminal status notifications
  </Card>

  <Card title="Data Types Reference" icon="brackets-curly" href="../reference">
    Complete schema documentation
  </Card>

  <Card title="Quickstart" icon="rocket" href="./quickstart">
    Review connection and authentication
  </Card>

  <Card title="Introduction" icon="book" href="../introduction">
    Return to Terminal Gateway overview
  </Card>
</CardGroup>
