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:
60seconds
MQTT Topics
All topics use the prefixlua/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 thecommand topic when the agent invokes a device command.
ResponseMessage
Published to theresponse topic after executing a command.
TriggerMessage
Published to thetrigger topic to fire an event to the agent.
TriggerAckMessage
Received on thetrigger_ack topic after the server processes a trigger.
StatusMessage (retained)
Published to thestatus 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 thestatus 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.
apiKey property in this non-retained message. Do not add apiKey when the CONNECT password is a typed device credential.
Heartbeat
Published to theheartbeat 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 acommandId that must be used for idempotency. Your client should:
- Maintain an LRU cache of recently seen
commandIdvalues (recommended: 1000 entries, 5-minute TTL) - On receiving a command, check if the
commandIdhas been seen before - If seen, re-publish the cached response without re-executing the handler
- If new, execute the handler, cache the response, then publish it
Rate Limits and Constraints
Example Implementations
- Go
- Rust
- C#
- Swift
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.

