Skip to content
Console

MQTT

Use MQTT when a broker is a better fit than a direct WSS or HTTPS connection.

MQTT is still a secure public protocol. Clients connect to the broker with TLS, authenticate with per-client credentials, and publish only to the topics allowed for that client.

Edge clients

Devices or small services need a broker-mediated path.

Network boundaries

The client can reach a broker more reliably than a direct hub route.

Fan-in traffic

Many clients need isolated topic access through one public broker endpoint.

Not the default

Use WSS first unless MQTT solves a real deployment problem.

  1. MQTT is enabled on the hub. WSS can stay the default while MQTT is optional.
  2. The broker is reachable over TLS. Public clients should use mqtts://.
  3. The client identity includes MQTT credentials. Create or download a fresh identity, or open Connection details, after enabling MQTT.
  4. Topic access is scoped to the client. Publish and subscribe only under the topic_prefix the identity returns.

The client identity carries a top-level crypto_key and, when MQTT is enabled, an mqtt block:

{
"crypto_key": "shared-encryption-key",
"mqtt": {
"endpoint": "mqtts://mqtt.thalovant.com:8883",
"username": "client-access-key",
"password": "client-broker-password",
"topic_prefix": "<namespace>/<hub-id>/<client-id>",
"tls": true
}
}
Field Meaning
crypto_key Top-level identity key for the encrypted message frames. It sits on the identity, not inside the mqtt block.
endpoint Broker URL. Public clients should use mqtts://.
username Broker username for this client. It is the client access key.
password Broker password for this client.
topic_prefix Full topic base for this client. Append a channel suffix instead of hardcoding a path.
tls Whether the client should connect over TLS.

The identity’s topic_prefix is the full base for every topic this client uses. It looks like <namespace>/<hub-id>/<client-id>, where <client-id> is the client access key. Treat it as opaque: read topic_prefix from the identity and append the channel suffix instead of assembling the path yourself.

Channel Topic Purpose
Inbound <topic_prefix>/in The client publishes requests here.
Outbound <topic_prefix>/out The client subscribes here for replies.
Status <topic_prefix>/status Retained presence and status for this client.

A client’s MQTT credentials come from its identity. To read them for a custom client:

  1. Open the connection. In Connections, select the client, then open its Connection details panel.
  2. Verify it is you. Confirm with your authenticator app or a recovery code. The panel is gated because it returns secrets.
  3. Copy the broker credentials. Take the endpoint (mqtts://mqtt.thalovant.com:8883), username (the access key), password, crypto key, and topic prefix. You can also download _identity.json.

If two-step sign-in blocks the panel, set it up first on Profile and Security.

You can connect any MQTT client to the broker with the credentials above, without Thalovant Voice or a managed runtime. Reaching the broker is the easy part.

The payload is not plaintext. Every message on <topic_prefix>/in and <topic_prefix>/out is the same encrypted message frame the WSS transport uses, negotiated with an AES-GCM handshake. The identity crypto_key is the shared key for those frames, so a raw MQTT publish of JSON connects but is never understood.

A custom client therefore has to speak the encrypted message protocol, not just authenticate to the broker. In practice, use a Thalovant SDK or a library that already implements the frame and handshake, then select the MQTT transport. The broker moves bytes; the encrypted protocol gives them meaning.

from thalovant import ThalovantClient, ThalovantControlPlane
api = ThalovantControlPlane()
api.login("[email protected]", "password")
result = api.create_client_identity(
"hub-id",
name="python-mqtt-client",
preferred_protocols=("mqtt", "wss"),
)
identity = result.identity
if identity.mqtt is None:
raise RuntimeError("MQTT is not available for this identity.")
with ThalovantClient(identity, protocol="mqtt") as client:
print(client.ask("Reply over MQTT.").text)
import { ThalovantClient, ThalovantControlPlane } from "@thalovant/sdk";
const api = new ThalovantControlPlane();
await api.login("[email protected]", "password");
const result = await api.createClientIdentity("hub-id", {
name: "node-mqtt-client",
preferredProtocols: ["mqtt", "wss"],
});
if (!result.identity.mqtt) {
throw new Error("MQTT is not available for this identity.");
}
const client = new ThalovantClient(result.identity, { protocol: "mqtt" });
try {
const reply = await client.ask("Reply over MQTT.");
console.log(reply.text);
} finally {
await client.close();
}
result, err := control.CreateClientIdentityForHubID(ctx, "hub-id", thalovant.BootstrapIdentityOptions{
Name: "go-mqtt-client",
PreferredProtocols: []thalovant.HubProtocol{
thalovant.ProtocolMQTT,
thalovant.ProtocolWSS,
},
})
if err != nil {
log.Fatal(err)
}
if result.Identity.MQTT == nil {
log.Fatal("MQTT is not available for this identity.")
}
client, err := thalovant.NewClientWithOptions(result.Identity, thalovant.ClientOptions{
Protocol: thalovant.ProtocolMQTT,
})
if err != nil {
log.Fatal(err)
}
defer client.Close(ctx)
let result = control
.create_client_identity_for_hub_id(
"hub-id",
BootstrapIdentityOptions {
name: "rust-mqtt-client".into(),
preferred_protocols: vec![HubProtocol::Mqtt, HubProtocol::Wss],
..Default::default()
},
)
.await?;
if result.identity.mqtt.is_none() {
panic!("MQTT is not available for this identity.");
}
let client = Client::with_protocol(result.identity.clone(), HubProtocol::Mqtt)?;
Symptom Check
The SDK says MQTT is unsupported Enable MQTT on the hub and create a fresh identity.
The identity has no mqtt block Enable MQTT on the hub, then open Connection details or download a fresh identity to pick up broker credentials.
Broker auth fails Confirm the username is the access key and the client uses the latest broker password. Rotate the identity if needed.
TLS fails Use the mqtts:// endpoint and confirm the broker certificate is valid.
A publish succeeds but nothing replies The payload must be an encrypted message frame, not plain JSON. Use a library that implements the protocol.
Messages are ignored Publish to <topic_prefix>/in and subscribe to <topic_prefix>/out. Do not invent topic paths.