Skip to main content

TagoTiP over MQTT

Tutorial

Follow our step-by-step tutorial to connect your first device over MQTT: TagoTiP MQTT: Quick Start

Publish/subscribe messaging with QoS delivery guarantees. TagoTiP over MQTT maps the protocol to standard MQTT topics - the server pushes commands to your device in real time, and QoS levels handle delivery reliability at the transport layer.

Endpoint​

RegionHostIPPorts
US-East-1mqtt.tip.us-e1.tago.io15.197.247.1461883 (MQTT) / 8883 (MQTTS)
EU-West-1mqtt.tip.eu-w1.tago.io166.117.88.1781883 (MQTT) / 8883 (MQTTS)
Use TLS in production

Port 8883 (MQTTS) for production. Port 1883 (MQTT) for development or when TLS is handled externally. When using port 1883, it is recommended to use the $tips/ topic prefix (TagoTiP/S) for application-layer security: see TagoTiP/S below.

Why MQTT?​

  • Pub/sub patterns - topic-based routing with flexible subscriptions
  • QoS delivery - at-most-once (0), at-least-once (1), or exactly-once (2)
  • Real-time commands - server pushes CMD to the device's ack topic instantly
  • Intermittent connectivity - designed for unreliable networks

Authentication​

The Authorization Hash (16 hex chars) is split across the MQTT CONNECT credentials:

FieldValueExample
UsernameFirst 8 hex chars of Authorization Hash4deedd7b
PasswordLast 8 hex chars of Authorization Hashab8817ec

The server reconstructs the full hash by concatenating username + password, then resolves the Account/Profile.

Context isolation​

All MQTT connections sharing the same credentials (derived from the same Authorization Token) belong to the same context. Any device in that context can publish or subscribe to any $tip/{serial}/... topic within it, enabling inter-device communication. If devices require isolation, use separate Authorization Tokens so each device operates in its own context.

Topic structure​

TagoTiP uses the $tip/ prefix for all protocol traffic:

TopicDirectionPurpose
$tip/{serial}/pushDevice -> ServerPublish data
$tip/{serial}/pullDevice -> ServerRequest last values
$tip/{serial}/ackServer -> DeviceResponses and commands

The device serial is embedded in the topic path, so it does not appear in the payload.

The device must subscribe to $tip/{serial}/ack at connect time to receive responses and commands.

TagoTiP/S​

TagoTiP/S provides application-layer encryption and is available on both ports. It is a good security option when not using TLS, for example, when connecting on port 1883.

To enable it, replace the $tip/ topic prefix with $tips/:

TopicDirectionPurpose
$tips/{serial}/pushDevice -> ServerPublish data
$tips/{serial}/pullDevice -> ServerRequest last values
$tips/{serial}/ackServer -> DeviceResponses and commands

The payload format and all protocol semantics are identical to the standard $tip/ topics. Only the prefix changes. The one exception is downlink commands, which are delivered on plain $tip/ only.

Enforce encrypted-only communication

When creating the device in TagoIO, set Protocol to "TagoTips only". The server will then reject any unencrypted traffic on $tip/ topics for that device, so all communication goes through TagoTiP/S.

Payload format​

The MQTT payload carries only the TagoTiP body, with an optional sequence counter prefix (!N|).

PUSH (publish to $tip/{serial}/push)​

Structured variables or passthrough:

[temp:=32#C;humidity:=65#%]
@=39.74,-104.99@1694567890000^batch_42[temp:=32#C]
>xDEADBEEF01020304
>b3q2+7wECAwQ=
!42|[temp:=32#C;humidity:=65#%]

PULL (publish to $tip/{serial}/pull)​

Comma-separated variable names:

temperature,humidity
!7|temperature,humidity

ACK (received on $tip/{serial}/ack)​

Status with optional detail and counter:

ACK|OK|3
ACK|!42|OK|2
ACK|OK|[temperature:=32#F@1694567890000;humidity:=65#%@1694567890000]
ACK|!7|OK|[temperature:=32#F@1694567890000]
ACK|ERR|invalid_payload
ACK|CMD|reboot
ACK|CMD|ota=https://example.com/v2.1.bin

When a sequence counter (!N) is present in the uplink, the server echoes it in the corresponding downlink so the device can correlate responses. Downlink commands are unsolicited and carry no counter.

Arduino example (ESP32)​

#include <WiFi.h>
#include <PubSubClient.h>

const char* SSID = "your-wifi";
const char* PASSWORD = "your-password";
const char* MQTT_HOST = "mqtt.tip.us-e1.tago.io";
const int MQTT_PORT = 1883;
const char* MQTT_USER = "4deedd7b"; // first 8 hex chars of auth hash
const char* MQTT_PASS = "ab8817ec"; // last 8 hex chars of auth hash
const char* SERIAL_N = "sensor-01";

WiFiClient wifi;
PubSubClient mqtt(wifi);

char pushTopic[64];
char ackTopic[64];

void onMessage(char* topic, byte* payload, unsigned int length) {
char msg[256];
memcpy(msg, payload, min(length, sizeof(msg) - 1));
msg[min(length, sizeof(msg) - 1)] = '\0';
Serial.print("ACK: ");
Serial.println(msg);

// check for commands
if (strncmp(msg, "ACK|CMD|", 8) == 0) {
Serial.print("Command: ");
Serial.println(msg + 8);
}
}

void setup() {
Serial.begin(115200);
WiFi.begin(SSID, PASSWORD);
while (WiFi.status() != WL_CONNECTED) delay(500);
Serial.println("WiFi connected");

snprintf(pushTopic, sizeof(pushTopic), "$tip/%s/push", SERIAL_N);
snprintf(ackTopic, sizeof(ackTopic), "$tip/%s/ack", SERIAL_N);

mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
}

void reconnect() {
while (!mqtt.connected()) {
if (mqtt.connect("esp32-client", MQTT_USER, MQTT_PASS)) {
mqtt.subscribe(ackTopic, 1);
Serial.println("MQTT connected");
} else {
delay(5000);
}
}
}

void loop() {
if (!mqtt.connected()) reconnect();
mqtt.loop();

static unsigned long lastSend = 0;
if (millis() - lastSend > 10000) {
lastSend = millis();

float temperature = analogRead(34) * 0.1;
char payload[128];
snprintf(payload, sizeof(payload), "[temperature:=%.1f#C]", temperature);

mqtt.publish(pushTopic, payload, false);
}
}

Quick test with mosquitto​

Replace the username/password with your Authorization Hash halves and sensor-01 with your serial. mosquitto_rr sends the message and waits for the response on the ack topic in a single connection.

Push a temperature reading​

mosquitto_rr -h mqtt.tip.us-e1.tago.io -p 1883 \
-u 4deedd7b -P ab8817ec \
-t '$tip/sensor-01/push' -e '$tip/sensor-01/ack' \
-m '[temperature:=25.5#C]'

Push multiple variables​

mosquitto_rr -h mqtt.tip.us-e1.tago.io -p 1883 \
-u 4deedd7b -P ab8817ec \
-t '$tip/sensor-01/push' -e '$tip/sensor-01/ack' \
-m '[temperature:=25.5#C;humidity:=60#%;active?=true]'

Push raw payload (passthrough)​

mosquitto_rr -h mqtt.tip.us-e1.tago.io -p 1883 \
-u 4deedd7b -P ab8817ec \
-t '$tip/sensor-01/push' -e '$tip/sensor-01/ack' \
-m '>xDEADBEEF01020304'

Raw bytes are delivered to your device's Payload Parser.

Pull the last stored values​

mosquitto_rr -h mqtt.tip.us-e1.tago.io -p 1883 \
-u 4deedd7b -P ab8817ec \
-t '$tip/sensor-01/pull' -e '$tip/sensor-01/ack' \
-m 'temperature,humidity'

Using TLS (port 8883)​

mosquitto_rr -h mqtt.tip.us-e1.tago.io -p 8883 --capath /etc/ssl/certs \
-u 4deedd7b -P ab8817ec \
-t '$tip/sensor-01/push' -e '$tip/sensor-01/ack' \
-m '[temperature:=25.5#C]'

Operators​

OperatorTypeExample
:=Numbertemperature:=25.5
=Stringstatus=online
?=Booleanactive?=true
@=Location (lat,lng or lat,lng,alt)position@=39.74,-104.99

Suffixes​

Append after the value, in this order:

SuffixPrefixExample
Unit#temperature:=25.5#C
Location@=speed:=10@=39.74,-104.99
Timestamp@temperature:=25.5@1694567890000
Group^temperature:=25.5^batch_01
Metadata{}temperature:=25.5{source=dht22}

All combined: temperature:=25.5#C@=39.74,-104.99@1694567890000^batch_01{source=dht22,quality=high}

Response codes​

Received on the $tip/{serial}/ack topic:

ResponseMeaning
ACK|OK|NN data points stored
ACK|OK|[...]PULL response with variable data
ACK|CMD|<command>Server command (delivered asynchronously)
ACK|ERR|invalid_tokenInvalid or expired credentials
ACK|ERR|device_not_foundSerial not found under your account
ACK|ERR|invalid_payloadMalformed payload
ACK|ERR|invalid_seqCounter not greater than last accepted
ACK|ERR|rate_limitedBack off and retry
ACK|ERR|payload_too_largePayload exceeds max size
ACK|ERR|server_errorRetry after a delay

Send a command with the TagoIO SDK. Construct the TagoTiP client with a Service Authorization token and call cmd() with the device serial, protocol, and command body.

FieldTypeRules
serialstringDevice serial, 1-100 chars. May not contain /, +, or #.
protocolstringOnly "mqtt" is accepted.
bodystringThe command. Non-empty UTF-8, max 15 KB (15,360 bytes).
const { TagoTiP } = require("@tago-io/sdk");

const tagoTiP = new TagoTiP({ token: "your-service-authorization-token" });

await tagoTiP.cmd({
serial: "sensor-01",
protocol: "mqtt",
body: "reboot",
});

The device (subscribed to $tip/sensor-01/ack) receives:

ACK|CMD|reboot

Responses​

StatusMeaning
200Command accepted for delivery. See best-effort note below.
400Authorization denied (missing/invalid token), Device can't be found, or invalid serial / protocol / body.
429Per-profile command rate limit exceeded.
500The command could not be accepted. Try again.

A 200 returns:

{ "status": true, "result": "Command sent" }
Best-effort delivery

A 200 means the command was accepted, not that the device received it. If no matching device is connected at that moment, the command is not delivered - there is no delivery receipt, retry, or persistence.

Delivery scope and limits​

  • Token-scoped: the command reaches only clients authenticated with the same authorization token that owns the device. Two tokens under the same profile stay isolated from each other.
  • Serial case: the ack topic $tip/{serial}/ack is matched verbatim and is case-sensitive. Firmware must subscribe with the lowercase serial for the command to arrive.
  • Plain $tip only: downlink commands are not supported on the encrypted $tips/ topics. A device set to TagoTips only rejects $tip/ traffic, so it cannot receive downlink commands.

Supported features​

FeatureSupport
MQTT versions3.1, 5
QoS levels0, 1, 2
TLSYes (port 8883, recommended for production)
TagoTiP/SYes
PublishIsolated per context
SubscribeIsolated per context
RetainNo
Last WillNo
Persistent SessionsNo
Offline MessagesNo

Limits​

Protocol limits​

LimitValue
Max topic length128 chars
Max payload size (wire)16,384 bytes
Max variables per payload100
Max metadata pairs32
Variable name length100 chars
Unit length25 chars
Serial length100 chars
Subscriptions per connection5

RPM = requests per minute.

Per-profile rate limits​

ResourceScaleStarterFree
Uplink RPM (PUSH)1,00050060
Downlink RPM (PULL)1,00050060
Connections per IP20103
Active connections per profile5005010
Subscription request RPM100505
Command RPM (POST /tip/cmd)1,00050060

Per-device limits​

ResourceScaleStarterFree
Max payload size100 KB100 KB100 KB
Connection TTL15 s10 s10 s
Keep-alive idle timeout5 s5 s5 s

Keepalive is handled natively by MQTT's PINGREQ/PINGRESP mechanism and does not require a TagoTiP-level PING.

Specification​

For the complete protocol grammar, parsing rules, and ABNF, see the TagoTiP Specification.