Skip to content
NEW AI Solution Creator — get a working IoT prototype in 10 minutes
Stand with Ukraine flag

The Things Stack Industries Integration

The Things Stack Industries integration connects ThingsBoard to a private The Things Industries (TTI) LoRaWAN network server over MQTT. ThingsBoard subscribes to the TTI MQTT API, receives uplink messages from your LoRaWAN devices, decodes the payload via the uplink converter, and stores the result as ThingsBoard telemetry and attributes. Downlink is optional: when configured, ThingsBoard encodes Rule Engine messages via the downlink converter and publishes them back to TTI, which delivers them to the device.

Use this integration when your LoRaWAN infrastructure runs on a private The Things Industries tenant rather than the public The Things Network. For public TTN connectivity, see the The Things Stack Community integration.

Before creating the integration, ensure:

  • You have access to ThingsBoard PE or ThingsBoard Cloud with integration functionality enabled for your tenant.
  • You have permissions to create integrations and data converters.
  • You have a The Things Industries account with at least one registered application and device.
  • At least one device is registered in your TTI application and is sending uplinks. If you have not registered a device yet, follow the Register an End Device step below.
  1. Log in to the The Things Industries console.
  2. Go to the Applications section and click Add application.
  3. Enter an Application ID — e.g. thingsboard-integration.
  4. Click Create application.

TTI exposes an MQTT broker for each application. ThingsBoard uses the credentials from this integration to subscribe to device uplinks and publish downlinks.

  1. In the TTI console, open your application and go to Other integrationsMQTT.
  2. Copy the Username and click Generate new API key to obtain the Password.
  3. Save both values — you will need them when configuring the ThingsBoard integration.
  1. In the application, open End devices and click Add end device.
  2. Configure the End device type:
    • Under Input method, select Enter end device specifics manually.
    • Frequency plan — e.g. Europe 863-870 MHz (SF9 for RX2 - recommended).
    • LoRaWAN version — e.g. LoRaWAN Specification 1.0.0.
  3. Click Show advanced activation, LoRaWAN class and cluster settings and set Activation mode to Activation by personalization (ABP).
  4. Fill in the Provisioning information:
    • DevEUI — click Generate. This becomes the device identifier in ThingsBoard.
    • Device address — click Generate.
    • AppSKey — click Generate.
    • NwkSKey — click Generate.
    • End device ID — e.g. thermostat-a. This value is used to route downlink messages.
  5. Click Add end device.

The uplink converter receives each TTI uplink message, decodes the LoRaWAN payload, and returns a structured object that ThingsBoard uses to create or update a device and store its telemetry and attributes.

If your device is in the built-in catalog, use the Library tab instead of writing a decoder — see Converters library for over 100 ready-made decoders.

For the full decoder function reference — all input parameters and output fields — see Uplink data converter.

  1. Go to Integrations center ⇾ Data converters.
  2. Click + Add data converter ⇾ Create new converter.

In the Add data converter dialog:

  1. Converter type — leave Uplink (selected by default).
  2. Integration type — in the search field, enter The Things Stack and select The Things Stack Industries from the list.
  3. Name — enter a converter name, for example TTI Uplink Converter.
  4. Configure main decoding parameters:
    • Device name — the default value Device $eui names each ThingsBoard device using the DevEUI from the uplink message (e.g. Device ABABABABABABABAA). On the first uplink ThingsBoard creates the device; on subsequent uplinks it updates the existing one.
    • The default decoder is pre-filled. Leave it unchanged for this guide.
      By default the editor opens in TBEL; use the TBEL / JS toggle (upper right) to switch languages.
    • /**
      * Decodes the incoming payload and returns a structured object containing telemetry data and attributes.
      *
      * @param {byte[]} input - The raw payload received as an array of bytes.
      * @returns {Object} output - The structured output with decoded telemetry and attributes.
      */
      function decodePayload(input) {
      // Initialize the output object with empty attributes and telemetry for clarity.
      var result = { attributes: {}, telemetry: {}};
      // Detect input type and normalize to byte array
      var bytes;
      if (metadata.payloadFormat == 'JSON') {
      // payloadFormat is 'JSON' when The Things Industries payload formatters are configured on the application or device level.
      // In that case, decoded_payload is a JSON object produced by your formatter.
      // This template assumes the formatter returns { "bytes": [...] } with the raw byte array.
      // If your formatter returns pre-decoded values in a different structure,
      // update this section to extract the values directly from 'parsed' instead.
      var parsed = decodeToJson(input);
      if (parsed.bytes == null) {
      throw new Error("Payload formatter output does not contain a 'bytes' field. " +
      "Update this decoder to match your formatter's output structure.");
      }
      bytes = parsed.bytes;
      } else {
      // payloadFormat is 'BINARY' when no payload formatter is configured — raw bytes from frm_payload are passed directly.
      bytes = input;
      }
      // Decode serial number (SN) from the first 4 bytes of the payload.
      // Press '?' icon in the top right corner to learn more about built in helper functions and capabilities.
      result.attributes.sn = parseBytesToInt(bytes, 0, 4);
      // Extract the timestamp from metadata (represented in milliseconds).
      var timestamp = metadata.ts; // ts is the timestamp parsed from the incoming message's time, or returns the current time if it cannot be parsed.
      // Combine the timestamp with decoded values and add it to the telemetry.
      result.telemetry = {
      ts: timestamp,
      values: {
      // Decode battery level from the 5th byte of the payload.
      battery: parseBytesToInt(bytes, 4, 1),
      // Decode temperature from the 6th and 7th bytes of the payload (divided by 100).
      temperature: parseBytesToInt(bytes, 5, 2) / 100.0,
      // Decode saturation from the 8th byte of the payload.
      saturation: parseBytesToInt(bytes, 7, 1)
      }
      };
      // Return the fully constructed output object.
      return result;
      // Same logic, less code:
      // return {
      // attributes: {
      // sn: parseBytesToInt(bytes, 0, 4)
      // },
      // telemetry: {
      // ts: convertDateToTimestamp(extractDateFromMetadata()),
      // values: {
      // battery: parseBytesToInt(bytes, 4, 1),
      // temperature: parseBytesToInt(bytes, 5, 2) / 100.0,
      // saturation: parseBytesToInt(bytes, 7, 1)
      // }
      // }
      // };
      }
      var result = decodePayload(payload);
      // Uncomment this code block to overwrite values set in the main configuration window. Useful if you extract device/asset/customer/group names from the payload;
      // result.type = 'DEVICE'; // Entity type allows you to choose type of created entity. Can be 'DEVICE' or 'ASSET'.
      // result.name = 'Temperature Sensor'; // Device or asset name (the value must be unique)
      // result.profile = 'IndustrialSensorProfile'; // Device or asset profile name.
      // result.customer = 'MyCustomer'; // If customer is not null - created entity will be assigned to customer with such name.
      // result.group = 'SensorsGroup'; // If group is not null - created entity will be added to the entity group with such name.
      // Return the final result object.
      return result;
  5. Review advanced decoding parameters — pre-populated for The Things Stack Industries; leave defaults unless your setup differs.

    These parameters control TTI network metadata — signal quality, location, and LoRa settings that ThingsBoard extracts automatically from each uplink message. The decoder script controls device sensor data — the fields written to result.attributes and result.telemetry.values.

    The default telemetry keys extracted from TTI messages include: fCnt, data, decoded, latitude, longitude, altitude, rssi, snr, channel.

    The default attribute keys include: eui, devAddr, fPort, bandwidth, spreadingFactor, codeRate, frequency, brandId, modelId, hardwareVersion, firmwareVersion, attributes, tenantId.

  6. Click Add.

What the Converter Receives

ThingsBoard passes two variables to the decoder function:

VariableTypeDescription
payloadbyte arrayThe LoRaWAN payload bytes. For BINARY format — raw bytes from frm_payload. For JSON format — bytes of the serialized decoded_payload object produced by the TTI formatter.
metadataobjectKey-value map populated from the TTI uplink MQTT message.

Key metadata fields available in the decoder:

FieldDescription
metadata.payloadFormat'BINARY' or 'JSON' — use this to branch your decoding logic. 'JSON' when a TTI payload formatter is configured.
metadata.tsUplink timestamp in milliseconds, parsed from the TTI received_at field; falls back to server time if absent.
metadata.euiDevEUI of the sending device, e.g. ABABABABABABABAA
metadata.devAddrDevice address assigned by the network
metadata.fPortLoRaWAN frame port number
metadata.spreadingFactorSpreading factor of the uplink transmission
metadata.rssiReceived signal strength indicator (dBm)
metadata.snrSignal-to-noise ratio (dB)

Example: BINARY Payload Decoded

The decoder reads byte ranges using parseBytesToInt(input, offset, length) and produces:

BytesFieldOutput typeExpressionNotes
0–3snattributeparseBytesToInt(bytes, 0, 4)Device serial number
4batterytelemetryparseBytesToInt(bytes, 4, 1)Battery level
5–6temperaturetelemetryparseBytesToInt(bytes, 5, 2) / 100.0Raw value ÷ 100
7saturationtelemetryparseBytesToInt(bytes, 7, 1)Saturation level

Example payload (hex):

00BC614E5F092950

Example output:

{
"attributes": { "sn": 12345678 },
"telemetry": {
"ts": 1684398325906,
"values": {
"battery": 95,
"temperature": 23.45,
"saturation": 80
}
}
}

Adapting the Converter

  • Different device name — change the Device name pattern in Main decoding configuration (e.g. replace Device $eui with $deviceId or a fixed string).
  • Different byte layout — adjust the offset and length in each parseBytesToInt() call to match your payload structure.
  • TTI payload formatter configured — if your TTI application has a JavaScript formatter, ThingsBoard receives the formatter’s output as a JSON object (payloadFormat == 'JSON'). Update the parsed.bytes extraction to match your formatter’s output structure, or map decoded fields directly: values: { temperature: parsed.temperature }.
  • Additional attributes — add more keys to result.attributes (e.g. result.attributes.firmwareVersion = parsed.fw).
  1. Go to Integrations center ⇾ Integrations and click + Add integration.
  2. Basic settings:
    • Select The Things Stack Industries as the integration type.
    • Enter a Name for the integration, or keep the default The Things Stack Industries integration.
    • Enable integration and Allow create devices or assets are enabled by default.
    • Click Next.
  3. Uplink data converter:
    • Click Select existing and choose the TTI Uplink Converter created in the previous step.
    • Alternatively, click Create new to define the decoder inline, or use Library to load a vendor-provided preset.
    • Click Next.
  4. Downlink data converter:
    • Click Skip — the downlink converter is only needed for sending commands to devices and can be added later.
  5. Connection settings:
    • Host type — leave Region selected.
    • Region — enter the identifier of the TTI cluster where your application is registered (e.g. eu1). ThingsBoard constructs the broker hostname as {region}.cloud.thethings.industries.
    • Port8883.
    • Credentials:
      • Username — the username from the TTI MQTT integration (e.g. my-thingsboard@thingsboard-test).
      • Password — the API key generated in the Obtain MQTT Credentials step.
    • Enable SSL is on by default — leave it enabled.
    Read more about each parameter in Connection Settings.
  6. Optionally click Check connection — the wizard shows Connected when successful.
  7. Click Add to complete the integration setup.

Host Type

ValueDescription
RegionConnect to a standard TTI cluster. Enter the Region identifier (e.g. eu1, nam1, au1) — ThingsBoard constructs the broker host as {region}.cloud.thethings.industries.
CustomSpecify a fully custom broker hostname — use this for private or self-hosted TTI deployments.

Port

ValueDescription
8883TLS-encrypted MQTT. Required when Enable SSL is on. Default and recommended for all TTI clusters.
1883Unencrypted MQTT. Only for local or private deployments where TLS is not available.

Credentials

ParameterValue
UsernameThe TTI MQTT integration username — format: {applicationId}@{tenantId} (e.g. thingsboard-integration@thingsboard).
PasswordThe API key generated via Integrations → MQTT → Generate new API key in the TTI console.

Enable SSL

Encrypts the MQTT connection using TLS. Enabled by default and required for all connections to TTI clusters (port 8883). Disable only when connecting to a local deployment on port 1883.

Topic Filters

Defines which MQTT topics ThingsBoard subscribes to for uplink messages.

FieldDescription
TopicMQTT topic filter. + matches a single topic level. Default: v3/+/devices/+/up (all devices across all applications accessible with the credentials).
QoSQuality of service level: 0 — at most once; 1 — at least once (may deliver duplicates); 2 — exactly once.

Downlink Topic Pattern

MQTT topic used to publish downlink messages to TTI. Default: v3/{username}/devices/${devId}/down/push, where {username} is the MQTT username (including the @tenantId suffix) and ${devId} is resolved from metadata.devId in the downlink converter output.

Execute Remotely

When enabled, ThingsBoard generates an Integration key and Integration secret that let the integration run as a separate process outside the ThingsBoard cluster — useful when the TTI broker is only reachable from a local network.

Advanced Settings

ParameterDefaultDescription
Protocol versionMQTT 3.1.1MQTT protocol version for the broker connection.
Max bytes in message32368Maximum payload size in bytes. Messages exceeding this limit are silently dropped.
Connection timeout (sec)10Seconds ThingsBoard waits for a broker response before marking the connection as failed.

After the integration is created, send a test uplink and confirm that ThingsBoard received the message, decoded it correctly, and provisioned the device.

Use the TTI console to simulate an uplink from the registered end device.

  1. In the TTI console, go to End devices and open thermostat-a — or the end device you registered earlier.
  2. Open the Messaging tab and click Simulate uplink.
  3. Leave FPort at its default value (1).
  4. In the Payload field, enter the test payload:
    00BC614E5F092950
  5. Click Simulate uplink. A Success — Uplink sent toast confirms the message was accepted.

This payload matches the byte layout from the uplink converter: sn = 12345678, battery = 95, temperature = 23.45, saturation = 80.

Go to Integrations center ⇾ Integrations, open The Things Stack Industries integration, and click the Events tab. Each successfully processed uplink appears as a row with status OK.

To inspect converter processing, go to Integrations center ⇾ Data converters, click TTI Uplink Converter, and open its Events tab:

  • In — the raw TTI message passed to the decoder.
  • Out — the decoded result: attributes and telemetry values written to ThingsBoard.
  • Metadata — the integration name and MQTT topic associated with the message.

Go to Entities ⇾ Devices. ThingsBoard automatically provisions a new device named Device <DevEUI> (e.g. Device 70B3D57ED80054FE) on the first uplink from each end device. Open the device and click the Latest Telemetry tab — you should see temperature, battery, and saturation decoded by the converter, plus network metadata fields such as rssi, snr, and latitude extracted from the TTI message.

To send a command to a TTI device from ThingsBoard, the Root Rule Chain must forward attribute changes to the TTI integration.

Two rule nodes are needed: an originator fields node that reads the TTI End device ID from the ThingsBoard device Label and injects it into message metadata, and an integration downlink node that sends the message to the integration.

  1. Open Rule Chains ⇾ Root Rule Chain and click the edit icon.
  2. In the node panel, search for originator fields and drag it onto the canvas.
  3. Configure the node:
    • Name — e.g. Retrieve Device ID.
    • Under Originator fields mapping, set Source field to Label and Target key to devId.
    • Set Add mapped originator fields to to Metadata.
    • Click Add.
  4. In the node panel, search for integration downlink and drag it onto the canvas.
  5. Configure the node:
    • Name — e.g. Downlink to TTI.
    • Integration — select The Things Stack Industries integration.
    • Click Add.
  6. Connect the message type switch node to the originator fields node using the Post attributes and Attributes Updated relations.
  7. Connect the originator fields node to the integration downlink node using the Success relation.
  8. Click Apply changes.

The downlink converter receives a Rule Engine message and encodes it into a TTI-compatible downlink payload. The converter output must follow this structure:

{
"contentType": "JSON",
"data": "{\"downlinks\":[{\"f_port\":2,\"frm_payload\":\"e3Bvd2VyU3RhdGU9b259\",\"priority\":\"NORMAL\"}]}",
"metadata": {
"devId": "thermostat-a"
}
}
FieldDescription
contentTypeEncoding of the data field: TEXT, JSON, or BINARY.
dataThe TTI downlink payload serialized as a string. Must contain a downlinks array with f_port, frm_payload (base64-encoded bytes), and priority. See the TTI MQTT API documentation for the full field list.
metadata.devIdTTI End device ID of the target device (e.g. thermostat-a). The Originator fields node injects this value from the ThingsBoard device’s Label.

For the full encoder function reference, see Downlink data converter.

Add the downlink converter to the existing integration:

  1. Go to Integrations center ⇾ Integrations and open The Things Stack Industries integration.
  2. Click the pencil icon to enter edit mode.
  3. In the Downlink data converter field, click Create new.
  4. Enter a name (e.g. TTI Downlink Converter), paste the encoder script below, and click Add.
  5. Click Apply changes to save the integration.
var devId = metadata.devId;
var data = {
downlinks: [{
f_port: 2,
confirmed: false,
frm_payload: btoa(msg),
priority: "NORMAL"
}]
};
var result = {
contentType: "JSON",
data: JSON.stringify(data),
metadata: {
devId: devId
}
};
return result;

The originator fields node injects metadata.devId from the ThingsBoard device’s Label. The full Rule Engine message is serialized to JSON and base64-encoded into frm_payload.

To adapt this converter:

  • Different port — change f_port to match the LoRaWAN port your device listens on.
  • Confirmed downlink — set confirmed: true to request an acknowledgement from the device. Use with caution — unacknowledged confirmed frames consume retries and may delay subsequent messages.
  • Selective payload — to send only a specific field, replace btoa(msg) with btoa(JSON.stringify({ key: msg.key })).
  • Different device routingmetadata.devId is populated from the device Label by the Originator fields node. If your ThingsBoard devices use a different field to store the TTI End device ID, update the Originator fields node mapping accordingly.

Trigger a downlink by adding a shared attribute to the ThingsBoard device provisioned during the uplink test:

  1. Go to Entities ⇾ Devices, open the device provisioned by the integration, and open the Attributes tab.
  2. Switch to Shared attributes and click +.
  3. Set the key (e.g. powerState) and a value (e.g. on).
  4. Click Add.

To verify the downlink was processed, go to Integrations center ⇾ Data converters, open TTI Downlink Converter, and click the Events tab. Click an event row to inspect:

  • In — the Rule Engine message received by the converter: msg contains the attribute payload; metadata.devId contains the TTI End device ID injected by the Originator fields node.
  • Out — the encoded output sent to TTI: a JSON downlinks array with frm_payload base64-encoded from the message body.

Go to the TTI console, open End devices ⇾ thermostat-a (or your device), and click the Live data tab — the downlink event appears in the list.

This section covers the most common problems encountered when setting up and running the The Things Stack Industries integration. Each entry describes the symptom, the most likely cause, and the steps to resolve it.

No Uplinks Received

SymptomCauseFix
No uplinks receivedWrong MQTT credentialsOpen the integration, click the pencil icon, go to the Connection step, and re-enter the credentials. The Username must be in the format {applicationId}@{tenantId} (e.g. my-thingsboard@thingsboard-test) — not your TTI account username. The Password is the TTI API key.
No uplinks receivedTTI API key expired or revokedIn the TTI console, open your application, go to Other integrations → MQTT, and generate a new API key. Update the integration password with the new key.
No uplinks receivedWrong cluster regionThe broker host is constructed as {region}.cloud.thethings.industries. Confirm the Region field matches the cluster your TTI application is registered on — e.g. eu1, nam1, or au1. Open your TTI application in the console and check the server address shown on the MQTT integration page.
No uplinks receivedSSL mismatchPort 8883 requires Enable SSL to be on. Port 1883 requires it to be off. Using port 8883 without SSL, or 1883 with SSL, causes a connection failure.
No uplinks receivedWrong topic filterThe default uplink topic filter is v3/+/devices/+/up. If you changed this value, open the integration in edit mode and verify the Topic field under Connection settings matches the uplink topic your TTI tenant uses.
Check connection passes but no uplinks arriveNo devices sending uplinksConfirm the end device is active and sending messages. In the TTI console, open the device and click Live data — uplinks appear in real time.
Check connection passes but no uplinks arriveDevice registered in a different applicationThe API key is scoped to one TTI application. Confirm the device is registered under the same application the MQTT credentials were generated for.

Uplink Received but Device Not Created

SymptomCauseFix
Uplink received, no device in ThingsBoardAllow create devices or assets is disabledOpen the integration, click the pencil icon, and enable Allow create devices or assets in the Basic settings step.
Uplink received, no device in ThingsBoardConverter returns empty output or device name is unresolvedGo to Integrations center ⇾ Data converters, open the uplink converter, and click the Events tab. Inspect the Out panel of a recent event — confirm name is present and non-empty. If the Device $eui template is used, confirm metadata.eui is populated in the In panel.
Uplink received, converter shows errorparsed.bytes == nullA TTI payload formatter is configured but its output does not include a bytes field. Either update the formatter to return { "bytes": [...] }, or update the decoder to extract values directly from the parsed object — for example, values: { temperature: parsed.temperature }. See Adapting the Converter.
Uplink received, converter shows errorByte offset out of rangeThe payload is shorter than the decoder expects. Open the converter Events tab, click a failed event, and compare the raw bytes in the In panel against the offsets in the decoder. Adjust the parseBytesToInt(bytes, offset, length) calls to match your device’s actual payload length.
Uplink received, converter shows errorTBEL or JavaScript exceptionOpen the uplink converter Events tab, click a failed event, and read the full stack trace in the Error panel. The trace includes the line number and variable name that caused the failure.

Telemetry Fields Missing or Have Wrong Values

SymptomCauseFix
Fewer fields than expectedByte offsets do not match the device payloadOpen the converter Events tab, click an event, and compare the raw bytes in the In panel against the decoded values in Out. Adjust the offset and length in each parseBytesToInt() call to match your device’s payload structure.
Temperature or other field reads as a large integerWrong payloadFormat branch activeIf a TTI payload formatter is configured, metadata.payloadFormat is 'JSON' and the decoder receives the JSON-serialized formatter output, not raw bytes. Open the In panel of a converter event and check metadata.payloadFormat — if it is 'JSON', confirm the if (metadata.payloadFormat == 'JSON') branch is correctly extracting values from the parsed object.
Field appears in Attributes instead of Latest TelemetryField assigned to the wrong output objectThingsBoard stores result.attributes as device attributes and result.telemetry.values as time-series telemetry. Move the field to the correct object in the decoder.

Downlink Not Delivered

SymptomCauseFix
Downlink converter Events tab is empty — no events triggeredRule Chain misconfiguredIn the Root Rule Chain, confirm the message type switch node is connected to the Originator fields node via the Attributes Updated and Post attributes relations, and that the Originator fields node connects to the Integration Downlink node via Success.
Downlink converter triggered but metadata.devId is emptyThingsBoard device Label is not setOpen the device in Entities ⇾ Devices, click the pencil icon, and set the Label field to the TTI End device ID (e.g. thermostat-a). The Originator fields node reads the device Label and injects it into metadata.devId. Without it, the downlink topic cannot be resolved and no message is sent to TTI.
Downlink always goes to the same devicedevId is hardcoded in the encoderThe encoder must read metadata.devId instead of using a hardcoded string. See Add Downlink Converter for the correct encoder script.
Downlink converter triggered, output looks correct, but no downlink in TTIDownlink topic pattern misconfiguredOpen the integration in edit mode, scroll to Advanced settings → Downlink topic pattern, and confirm the pattern is v3/{username}/devices/${devId}/down/push where {username} includes the full @tenantId suffix.
Downlink delivered to TTI but device does not respondconfirmed: true with no acknowledgementIf the downlink uses confirmed: true, the device must send an uplink to acknowledge. If the device is out of range or offline, confirmed frames are retried until the retry limit is reached, delaying subsequent messages. Switch to confirmed: false unless ACK tracking is required.

Connection Check Fails

SymptomCauseFix
”Connection refused”Wrong port or SSL settingConfirm port 8883 with Enable SSL on, or port 1883 with Enable SSL off.
”Connection timeout”Wrong cluster hostnameConfirm the Region is set correctly and the host resolves to {region}.cloud.thethings.industries. Switch Host type to Custom and enter the full hostname directly to rule out a region lookup issue.
”Unauthorized” or “Bad credentials”Invalid username or passwordRe-enter the username in {applicationId}@{tenantId} format and paste a freshly generated API key as the password.

How to Read Debug Events

  1. Go to Integrations center ⇾ Integrations, open The Things Stack Industries integration, and click the Events tab.
  2. Click an event row to inspect:
    • In — the raw MQTT message received from TTI before processing.
    • Out — what the converter returned: device name, attributes, and telemetry values passed to ThingsBoard.
    • Error — error text and stack trace, if processing failed.
  3. For converter-level details, go to Integrations center ⇾ Data converters, open TTI Uplink Converter, and click the Events tab — the same In / Out / Error panels are available there.

Enable Debug mode on the integration to capture all raw input/output events. Starting from ThingsBoard 3.9, full debug events are stored only during the first hour — afterward, only error events are retained. Disable debug mode once the issue is identified.