MQTT API definitions for ESP RainMaker Neo platform — User (mobile app / web client) perspective.
This spec documents every MQTT topic a user publishes to or subscribes to, for controlling devices and receiving real-time state updates.
Users connect to AWS IoT Core over WebSocket with SigV4 (not X.509 certificates):
POST /v1/user/credentials with ID Token → receive Cognito Identity ID + temporary AWS credentialsPOST /v1/user/assume_role → receive scoped temporary credentials restricted to the user's groups/subgroups. To request S3/KVS credentials instead, call POST /v1/groups/{group_id}/nodes/{node_id}/assumed-roles with { "services": ["s3"|"kvs"] } — the response scopes to that single node and contains no IoT/MQTT permissions.wss://<iot-endpoint>:443/mqtt using a SigV4-signed request with the scoped credentials, and an MQTT client id of user:<email|phone>:<session> (see Client ID below)The MQTT client id must be user:<login-username>:<session>, where <login-username>
is the caller's own login email or phone number and <session> is unique per app session
(a random number or timestamp is fine — it need not be secret).
The vended session policy scopes iot:Connect to client/user:<login-username>:*, so:
user:<login-username> form (no session suffix) is rejected — the connect
fails with AWS_ERROR_MQTT_UNEXPECTED_HANGUP;: delimiter cannot appear in an
email or E.164 phone number, so the wildcard never matches another user;Topic-level access is enforced via IAM session policies scoped to the user's group/subgroup membership.
AWS IoT Core endpoint (WebSocket + SigV4 authentication)
WebSocket MQTT connection authenticated via AWS SigV4 with Cognito-issued temporary credentials.
The full SigV4 handshake uses several query parameters (X-Amz-Algorithm, X-Amz-Credential,
X-Amz-Date, X-Amz-SignedHeaders, X-Amz-Signature, X-Amz-Security-Token);
X-Amz-Signature is declared here as the canonical identifier for the scheme.
[User → Device] Send parameter control commands to a specific device.
User sends parameter control commands to a specific device
Available only on servers:
Unique device identifier (Thing Name / Node ID)
Named shadow identifier: params-<group_id>[-<sub1>-<sub2>-…].
Subgroup IDs are sorted alphabetically. Derived from the device's group membership.
For unassociated nodes (no group), the shadow name is params- (empty suffix).
Accepts the following message:
Parameter update command sent by user to device(s) — device-name addressed
Same format for unicast and group params broadcast.
Default data model: { "<device_name>": { "<param>": <value> } }.
Matter data model: { "<endpoint_hex>": { "c": { "s|c": { "<cluster_hex>": { "c": { "<cmd_hex>": "0x<TLV>" } } } } } }.
{
"Light": {
"Power": true,
"Brightness": 80
}
}
{
"Light": {
"Power": true,
"Brightness": 80
},
"Fan": {
"Speed": 3,
"Power": true
}
}
{
"Thermostat": {
"Temperature": 22.5,
"Mode": "cool"
}
}
{
"0x1": {
"c": {
"s": {
"0x6": {
"c": {
"0x2": "0x1518"
}
}
}
}
}
}
[User → All devices in group] Device-type-addressed control commands. Each receiving node applies the payload only to its own devices whose type matches a top-level key.
User sends device-type-addressed control commands to all devices in a group
Available only on servers:
Group ID for group-wide operations
Accepts the following message:
Control command broadcast to all nodes in a group or subgroup
Control payload broadcast on rainmaker/nodes/groups/{groupId}/control (group)
or rainmaker/nodes/groups/{groupId}/subgroups/{subgroupId}/control (subgroup).
Default data model: top-level keys are device types (e.g.
esp.device.light). Each device's value is an object that holds nested
sub-keys describing what to apply — currently only params is defined,
with room for additional sub-keys (e.g. cmd, meta) in future without
another topic rename. Receiving nodes apply the payload only to their own
devices that match the listed device types; other devices are ignored.
Matter data model: command invocations only. Same nested structure as
the unicast ParamControl message, except the top-level endpoint key is the
wildcard endpoint "0xffff" (not a specific endpoint): 0xffff → c →
s|c → cluster → c → { command hex → TLV hex }. Each node invokes the
command on every endpoint that supports the addressed cluster + command,
resolved from its own support list.
Default: map of device type → control envelope. Matter: the unicast ParamControl command structure.
{
"esp.device.light": {
"params": {
"esp.param.power": true
}
}
}
{
"esp.device.light": {
"params": {
"esp.param.power": true,
"esp.param.brightness": 75
}
},
"esp.device.fan": {
"params": {
"esp.param.power": false
}
}
}
{
"*": {
"params": {
"esp.param.power": false
}
}
}
{
"0xffff": {
"c": {
"s": {
"0x6": {
"c": {
"0x2": "0x1518"
}
}
}
}
}
}
[User → All devices in subgroup] Device-type-addressed control commands
for one specific subgroup. Same payload shape as the group-level
groupControlBroadcast channel.
User sends device-type-addressed control commands to all devices in a subgroup
Available only on servers:
Group ID for group-wide operations
Subgroup ID for subgroup-scoped operations
Accepts the following message:
Control command broadcast to all nodes in a group or subgroup
Control payload broadcast on rainmaker/nodes/groups/{groupId}/control (group)
or rainmaker/nodes/groups/{groupId}/subgroups/{subgroupId}/control (subgroup).
Default data model: top-level keys are device types (e.g.
esp.device.light). Each device's value is an object that holds nested
sub-keys describing what to apply — currently only params is defined,
with room for additional sub-keys (e.g. cmd, meta) in future without
another topic rename. Receiving nodes apply the payload only to their own
devices that match the listed device types; other devices are ignored.
Matter data model: command invocations only. Same nested structure as
the unicast ParamControl message, except the top-level endpoint key is the
wildcard endpoint "0xffff" (not a specific endpoint): 0xffff → c →
s|c → cluster → c → { command hex → TLV hex }. Each node invokes the
command on every endpoint that supports the addressed cluster + command,
resolved from its own support list.
Default: map of device type → control envelope. Matter: the unicast ParamControl command structure.
{
"esp.device.light": {
"params": {
"esp.param.power": true
}
}
}
{
"esp.device.light": {
"params": {
"esp.param.power": true,
"esp.param.brightness": 75
}
},
"esp.device.fan": {
"params": {
"esp.param.power": false
}
}
}
{
"*": {
"params": {
"esp.param.power": false
}
}
}
{
"0xffff": {
"c": {
"s": {
"0x6": {
"c": {
"0x2": "0x1518"
}
}
}
}
}
}
[Shadow → User] Full shadow document on every state change.
Contains both current and previous state. User subscribes to receive
real-time state updates from devices.
User receives real-time device state changes (shadow update documents)
Available only on servers:
Unique device identifier (Thing Name / Node ID)
Named shadow identifier: params-<group_id>[-<sub1>-<sub2>-…].
Subgroup IDs are sorted alphabetically. Derived from the device's group membership.
For unassociated nodes (no group), the shadow name is params- (empty suffix).
Accepts the following message:
Full shadow document after state change
Contains both current and previous full shadow state.
Used by apps to detect exactly what changed.
{
"previous": {
"state": {
"reported": {
"params": {
"Light": {
"Power": true,
"Brightness": 50
}
},
"online": true,
"ncfg_ver": 1743656583
}
}
},
"current": {
"state": {
"reported": {
"params": {
"Light": {
"Power": true,
"Brightness": 80
}
},
"online": true,
"ncfg_ver": 1743656583
}
}
},
"timestamp": 1743656590
}
[Shadow → User] Shadow delta — contains only the difference between desired and reported state. Useful for tracking pending commands.
User receives shadow delta (desired vs. reported difference)
Available only on servers:
Unique device identifier (Thing Name / Node ID)
Named shadow identifier: params-<group_id>[-<sub1>-<sub2>-…].
Subgroup IDs are sorted alphabetically. Derived from the device's group membership.
For unassociated nodes (no group), the shadow name is params- (empty suffix).
Accepts the following message:
Difference between desired and reported state
Contains only the fields where desired differs from reported. Useful for tracking commands that haven't been applied yet.
{
"version": 5,
"timestamp": 1743656583,
"state": {
"params": {
"Light": {
"Brightness": 80
}
}
},
"metadata": {}
}
[Shadow → User] Confirmation that a shadow update was accepted by AWS IoT.
User receives confirmation that a shadow update was accepted
Available only on servers:
Unique device identifier (Thing Name / Node ID)
Named shadow identifier: params-<group_id>[-<sub1>-<sub2>-…].
Subgroup IDs are sorted alphabetically. Derived from the device's group membership.
For unassociated nodes (no group), the shadow name is params- (empty suffix).
Accepts the following message:
Shadow update accepted confirmation
{
"state": {
"reported": {},
"desired": {}
},
"metadata": {},
"version": 5,
"timestamp": 1743656583
}
[Shadow → User] Rejection of a shadow update (e.g. version conflict).
User receives rejection of a shadow update
Available only on servers:
Unique device identifier (Thing Name / Node ID)
Named shadow identifier: params-<group_id>[-<sub1>-<sub2>-…].
Subgroup IDs are sorted alphabetically. Derived from the device's group membership.
For unassociated nodes (no group), the shadow name is params- (empty suffix).
Accepts the following message:
Shadow update rejected
{
"code": 409,
"message": "Version conflict",
"timestamp": 1743656583,
"clientToken": "string"
}
[User → Shadow] Request the current shadow document, e.g. to fetch
device state on connect or app resume. The document is returned on
.../get/accepted. The update/* topics above only fire on subsequent
changes, so this round-trip is how an app reads current state.
User requests the current shadow document (response on get/accepted)
Available only on servers:
Unique device identifier (Thing Name / Node ID)
Named shadow identifier: params-<group_id>[-<sub1>-<sub2>-…].
Subgroup IDs are sorted alphabetically. Derived from the device's group membership.
For unassociated nodes (no group), the shadow name is params- (empty suffix).
Accepts the following message:
Empty JSON
{}
[Shadow → User] Current shadow document returned in response to a get.
User receives the current shadow document in response to a get
Available only on servers:
Unique device identifier (Thing Name / Node ID)
Named shadow identifier: params-<group_id>[-<sub1>-<sub2>-…].
Subgroup IDs are sorted alphabetically. Derived from the device's group membership.
For unassociated nodes (no group), the shadow name is params- (empty suffix).
Accepts the following message:
Shadow update accepted confirmation
{
"state": {
"reported": {},
"desired": {}
},
"metadata": {},
"version": 5,
"timestamp": 1743656583
}
Parameter update command sent by user to device(s) — device-name addressed
Same format for unicast and group params broadcast.
Default data model: { "<device_name>": { "<param>": <value> } }.
Matter data model: { "<endpoint_hex>": { "c": { "s|c": { "<cluster_hex>": { "c": { "<cmd_hex>": "0x<TLV>" } } } } } }.
Control command broadcast to all nodes in a group or subgroup
Control payload broadcast on rainmaker/nodes/groups/{groupId}/control (group)
or rainmaker/nodes/groups/{groupId}/subgroups/{subgroupId}/control (subgroup).
Default data model: top-level keys are device types (e.g.
esp.device.light). Each device's value is an object that holds nested
sub-keys describing what to apply — currently only params is defined,
with room for additional sub-keys (e.g. cmd, meta) in future without
another topic rename. Receiving nodes apply the payload only to their own
devices that match the listed device types; other devices are ignored.
Matter data model: command invocations only. Same nested structure as
the unicast ParamControl message, except the top-level endpoint key is the
wildcard endpoint "0xffff" (not a specific endpoint): 0xffff → c →
s|c → cluster → c → { command hex → TLV hex }. Each node invokes the
command on every endpoint that supports the addressed cluster + command,
resolved from its own support list.
Default: map of device type → control envelope. Matter: the unicast ParamControl command structure.
Full shadow document after state change
Contains both current and previous full shadow state.
Used by apps to detect exactly what changed.
Difference between desired and reported state
Contains only the fields where desired differs from reported. Useful for tracking commands that haven't been applied yet.
Shadow update accepted confirmation
Shadow update rejected
AWS IoT Shadow document structure