Skip to main content

Overview

Lua devices communicate with agents over MQTT or Socket.IO. This page documents the wire protocol for clients in languages without an official SDK.
If you’re using Node.js, Python, or MicroPython, use the official SDKs instead. This page is for building clients in languages without an official SDK.

Authentication

First, provision a device credential for the exact agent, device name, and operations that your client uses. Then pass these values as MQTT connection parameters: MQTT username format: {agentId}:{deviceName} Existing raw MQTT clients may keep using a non-dotted legacy key as the password indefinitely. Connection settings:
  • Broker: wss://mqtt.heylua.ai/mqtt (TLS required in production)
  • Client ID: lua-{agentId}-{deviceName}
  • Clean session: false (enables persistent session for QoS 1 message queueing)
  • Keep-alive: 60 seconds

MQTT Topics

All topics use the prefix lua/devices/{agentId}/{deviceName}/.

Device Subscribes To (Server -> Device)

Device Publishes To (Device -> Server)

Socket.IO Events

If you prefer WebSocket transport, connect to {serverUrl}/devices with Socket.IO. Socket.IO auth is passed in the auth option at connection time: The historical wire field is named apiKey even when it carries a device credential.
auth.apiKey is the stable Socket.IO wire field for both device credentials and existing legacy keys. Do not rename this protocol field.

Message Schemas

CommandMessage

Received on the command topic when the agent invokes a device command.

ResponseMessage

Published to the response topic after executing a command.

TriggerMessage

Published to the trigger topic to fire an event to the agent.

TriggerAckMessage

Received on the trigger_ack topic after the server processes a trigger.

StatusMessage (retained)

Published to the status topic with retain: true. This message must not contain a credential because the broker stores retained messages and delivers them to future subscribers.

CommandManifestStatusMessage (non-retained)

Published to the status topic with retain: false immediately after the retained status. A client that uses a device credential sends its command manifest without repeating the credential. The gateway uses the identity established during MQTT CONNECT.
An existing client that uses a non-dotted legacy key keeps the apiKey property in this non-retained message. Do not add apiKey when the CONNECT password is a typed device credential.

Heartbeat

Published to the heartbeat topic every 30 seconds with an empty payload. QoS 0 (fire-and-forget).

Last Will and Testament (LWT)

Set the MQTT LWT to publish an offline status if the device disconnects unexpectedly:
  • Topic: lua/devices/{agentId}/{deviceName}/status
  • Payload: {"status": "offline", "timestamp": "..."}
  • QoS: 1
  • Retain: true

Self-Describing Commands

Commands are sent at connect time in the CommandManifestStatusMessage. The server registers permitted commands as agent tools automatically.

Command Lifecycle

Idempotency

Commands include a commandId that must be used for idempotency. Your client should:
  1. Maintain an LRU cache of recently seen commandId values (recommended: 1000 entries, 5-minute TTL)
  2. On receiving a command, check if the commandId has been seen before
  3. If seen, re-publish the cached response without re-executing the handler
  4. If new, execute the handler, cache the response, then publish it
This ensures that retried messages (common with QoS 1) do not cause duplicate side effects.
Without idempotency handling, MQTT QoS 1 redelivery can cause commands to execute multiple times. Always implement the dedup cache.

Rate Limits and Constraints

Example Implementations

These examples show the minimal connect-and-handle pattern. Production clients should add: idempotency dedup, heartbeat loop, LWT, graceful shutdown, error handling, and auto-reconnect with exponential backoff.