UDP Integration
UDP Integration lets devices that use their own text or binary protocol send data to ThingsBoard as UDP datagrams. An uplink data converter transforms each datagram into device or asset data, including telemetry and attributes. ThingsBoard can also send a downlink datagram back to the device’s source address. UDP Integration runs only as a remote integration: a standalone process that binds the UDP port and connects to ThingsBoard over gRPC.
Use UDP Integration when:
- Devices send a custom text or binary protocol over raw UDP.
- Firmware sends fire-and-forget datagrams to a fixed host and port and cannot be changed.
- Low transport overhead matters more than delivery guarantees, for example for battery-powered sensors or high-frequency telemetry.
Architecture
Section titled “Architecture”- Receive data. A device sends a datagram to the configured port (default
11560) on the remote integration host. The handler turns it into the converter payload: text, JSON, raw bytes, or a hex envelope. - Decode the payload. The uplink converter identifies the device or asset and extracts attributes and telemetry.
- Process the message. ThingsBoard processes the converted data through the Rule Engine. If Allow create devices or assets is enabled, ThingsBoard can create an entity that does not exist yet.
- Send downlink data (optional). The integration remembers each device’s source address. The downlink converter encodes the Rule Engine message, and the integration sends it as a datagram to that address.
Prerequisites
Section titled “Prerequisites”- Access to ThingsBoard Cloud with integrations enabled for your tenant.
- Permission to create integrations and data converters.
- A host for the remote integration process. Incoming UDP traffic must reach the integration port (default
11560), and the host needs outbound access to the ThingsBoard gRPC port (9090by default). - A sample of the payload your device sends.
nc(netcat) to send test datagrams.
Set Up UDP Integration
Section titled “Set Up UDP Integration”Create the uplink converter, assign it to a new UDP integration, start the remote integration process, and send a test datagram.
In this guide, device Thermostat-01 sends its serial number and temperature to port 11560. The integration supports four payload formats:
Comma-separated values with a fixed field order (deviceName,deviceType,serialNumber,temperature):
Thermostat-01,thermostat,SN-12345,25.5{ "deviceName": "Thermostat-01", "deviceType": "thermostat", "serialNumber": "SN-12345", "temperature": 25.5}35 bytes:
\x54\x68\x65\x72\x6d\x6f\x73\x74\x61\x74\x2d\x30\x31\x74\x68\x65\x72\x6d\x6f\x73\x74\x61\x74\x53\x4e\x2d\x31\x32\x33\x34\x35\x32\x35\x2e\x35| Bytes | Value |
|---|---|
0–12 (\x54\x68\x65\x72\x6d\x6f\x73\x74\x61\x74\x2d\x30\x31) |
Device name: Thermostat-01 |
13–22 (\x74\x68\x65\x72\x6d\x6f\x73\x74\x61\x74) |
Device type: thermostat |
23–30 (\x53\x4e\x2d\x31\x32\x33\x34\x35) |
Serial number: SN-12345 |
31–34 (\x32\x35\x2e\x35) |
Temperature: 25.5 |
The same 35 bytes written as a hex string. The test command converts it to bytes with xxd -r -p before sending:
546865726d6f737461742d3031746865726d6f73746174534e2d313233343532352e35| Hex segment | Value |
|---|---|
546865726d6f737461742d3031 |
Device name: Thermostat-01 |
746865726d6f73746174 |
Device type: thermostat |
534e2d3132333435 |
Serial number: SN-12345 |
32352e35 |
Temperature: 25.5 |
Create the Uplink Data Converter
Section titled “Create the Uplink Data Converter”The uplink converter reads the payload prepared by the handler and maps it to ThingsBoard entities and data. UDP Integration uses the Generic converter: you write the decoder in TBEL or JavaScript. For converter types and the full decoder reference, see Uplink data converter.
-
Go to Integrations ⇾ Data converters and click + Add data converter ⇾ Create new converter.
-
Converter type — keep Uplink.
-
Integration type — select UDP.
-
Name — enter a name, for example
UDP Uplink Converter. -
Main decoding configuration — the editor is pre-filled with the default decoder template for JSON payloads. For JSON, you can keep it. For text, binary, or hex payloads, replace it with the decoder for your payload format below and, if needed, adapt the field names to your device. The editor opens in TBEL; use the TBEL / JS toggle to switch to JavaScript.
/** Decoder **/// decode payload to stringvar strArray = decodeToString(payload);var payloadArray = strArray.replaceAll("\"", "").replaceAll("\\\\n", "").split(',');// Result object with device attributes/telemetry datavar result = {deviceName: payloadArray[0],deviceType: payloadArray[1],attributes: {serialNumber: payloadArray[2]},telemetry: {temperature: parseFloat(payloadArray[3])}};/** Helper functions 'decodeToString' and 'decodeToJson' are already built-in **/return result;/** Decoder **/// decode payload to stringvar strArray = decodeToString(payload);var payloadArray = strArray.replace(/"/g, "").replace(/\s/g, "").replace(/\\n/g, "").split(',');// Result object with device attributes/telemetry datavar result = {deviceName: payloadArray[0],deviceType: payloadArray[1],attributes: {serialNumber: payloadArray[2]},telemetry: {temperature: parseFloat(payloadArray[3])}};/** Helper functions **/function decodeToString(payload) {return String.fromCharCode.apply(String, payload);}return result;Expected format:
deviceName,deviceType,serialNumber,temperature— fixed field order.To adapt:
- Another delimiter — replace
split(','), for example withsplit(';'). - Another field order — change the
payloadArrayindexes. - More values — add them to
telemetry, for examplebatteryLevel: parseFloat(payloadArray[4]). - Attributes or telemetry — move a field between the
attributesandtelemetrymaps.
/** Decoder **/// decode payload to JSONvar data = decodeToJson(payload);// Result object with device attributes/telemetry datavar result = {deviceName: data.deviceName,deviceType: data.deviceType,attributes: {serialNumber: data.serialNumber},telemetry: {temperature: data.temperature}};/** Helper functions 'decodeToString' and 'decodeToJson' are already built-in **/return result;/** Decoder **/// decode payload to JSONvar data = decodeToJson(payload);// Result object with device attributes/telemetry datavar result = {deviceName: data.deviceName,deviceType: data.deviceType,attributes: {serialNumber: data.serialNumber},telemetry: {temperature: data.temperature}};/** Helper functions **/function decodeToString(payload) {return String.fromCharCode.apply(String, payload);}function decodeToJson(payload) {var str = decodeToString(payload);var data = JSON.parse(str);return data;}return result;The default template already decodes JSON: it reads
deviceNameanddeviceType, storesserialNumberas an attribute, takestsortimestampas the time, and stores all other fields as telemetry. It rejects a message withoutdeviceNamewith a clear error in the debug events. Keep it as is, or use the shorter example above if you want to map only specific fields.The example parses the payload as JSON and maps its fields to
deviceName,deviceType, attributes, and telemetry.To adapt:
- Replace
data.deviceName/data.deviceTypewith the actual field names your device sends (e.g.data.id,data.model). - Add or remove telemetry fields to match your sensor output:
pressure: data.pressure, co2: data.co2. - Move fields to
attributesif they represent device properties rather than time-series data (e.g.firmware: data.fw).
/** Decoder **/// decode payload to stringvar payloadStr = decodeToString(payload);var deviceName = payloadStr.substring(0, 13);var deviceType = payloadStr.substring(13, 23);// Result object with device attributes/telemetry datavar result = {deviceName: deviceName,deviceType: deviceType,attributes: {serialNumber: payloadStr.substring(23, 31)},telemetry: {temperature: parseFloat(payloadStr.substring(31, 35))}};/** Helper functions 'decodeToString' and 'decodeToJson' are already built-in **/return result;/** Decoder **/// decode payload to stringvar payloadStr = decodeToString(payload);var deviceName = payloadStr.substring(0, 13);var deviceType = payloadStr.substring(13, 23);// Result object with device attributes/telemetry datavar result = {deviceName: deviceName,deviceType: deviceType,attributes: {serialNumber: payloadStr.substring(23, 31)},telemetry: {temperature: parseFloat(payloadStr.substring(31, 35))}};/** Helper functions **/function decodeToString(payload) {return String.fromCharCode.apply(String, payload);}function decodeToJson(payload) {var str = decodeToString(payload);var data = JSON.parse(str);return data;}return result;The decoder converts the bytes to a string and reads each field from a fixed offset.
To adapt:
- Adjust the
substring(start, end)offsets to match your device’s byte layout. - For integer fields stored as raw bytes (not ASCII digits), use
parseBytesToInt(payload, offset, length, bigEndian)instead ofparseFloat(payloadStr.substring(...)). For example:temperature: parseBytesToInt(payload, 31, 2, true) / 100.0. - If the device name or type is fixed rather than embedded in the payload, replace the
substringcalls with string literals. - Add more telemetry fields by reading additional byte ranges and adding them to the
telemetrymap.
/** Decoder **/// decode payload to JSONvar data = decodeToJson(payload).reports[0].value;// Result object with device attributes/telemetry datavar result = {deviceName: hexToString(data.substring(0, 26)),deviceType: hexToString(data.substring(26, 46)),attributes: {serialNumber: hexToString(data.substring(46, 62))},telemetry: {temperature: parseFloat(hexToString(data.substring(62, 70)))}};/** Helper functions **/// Hexadecimal string to stringfunction hexToString(hex) {return bytesToString(hexToBytes(hex));}return result;/** Decoder **/// decode payload to JSONvar data = decodeToJson(payload).reports[0].value;// Result object with device attributes/telemetry datavar result = {deviceName: hexToString(data.substring(0, 26)),deviceType: hexToString(data.substring(26, 46)),attributes: {serialNumber: hexToString(data.substring(46, 62))},telemetry: {temperature: parseFloat(hexToString(data.substring(62, 70)))}};/** Helper functions **/function decodeToString(payload) {return String.fromCharCode.apply(String, payload);}// Hexadecimal string to stringfunction hexToString(hex) {var str = '';for (var i = 0; i < hex.length; i += 2) {var notNullValue = parseInt(hex.substr(i, 2), 16);if (notNullValue) {str += String.fromCharCode(notNullValue);}}return str;}function decodeToJson(payload) {var str = decodeToString(payload);var data = JSON.parse(str);return data;}return result;The decoder reads the hex string from
reports[0].valueof the handler’s JSON envelope and converts each segment back to ASCII.To adapt:
- Adjust the
substring(start, end)offsets to match your device’s hex-encoded field layout (each ASCII character encodes to 2 hex digits, so the 13-character nameThermostat-01occupies 26 hex characters). - For numeric fields not represented as ASCII digits, use
parseInt(hexToString(data.substring(start, end)), 16)to decode a raw integer. - If your device sends a flat hex string rather than a JSON envelope, replace
decodeToJson(payload).reports[0].valuewithdecodeToString(payload)and adjust offsets accordingly.
- Another delimiter — replace
-
Click Test decoder function and check the result (see Test the Decoder).
-
Click Add.
Decoder Inputs
The decoder signature is function decoder(payload, metadata): object | object[].
| Variable | Type | Content |
|---|---|---|
payload |
byte array | The datagram, prepared by the handler type (see the table below). |
metadata |
{[key: string]: string} |
integrationName and the key–value pairs from the integration’s Metadata field. The sender’s IP address and port are not included. |
What reaches payload for each handler type:
| Handler type | payload content |
|---|---|
| Text | The datagram decoded with Charset Name and serialized as a JSON string, so the text arrives in double quotes. The Text decoder template strips them. Empty datagrams are ignored. |
| JSON | The datagram bytes, unchanged. An empty datagram also reaches the decoder, and JSON parsing fails. |
| Binary | The datagram bytes, unchanged. Empty datagrams are ignored. |
| Hex | A JSON envelope with the datagram as a lowercase hex string: {"reports":[{"value":"546865..."}]}. The Hex decoder template reads reports[0].value. An empty datagram arrives as {"reports":[{"value":""}]}. |
Built-in helpers include decodeToString(payload), decodeToJson(payload), and, in TBEL, hexToBytes, bytesToString, and parseBytesToInt. For other patterns, see Common scripting patterns.
Test the Decoder
Click Test decoder function below the editor. The dialog opens with pre-filled Metadata rows, Payload content type set to JSON, and a sample JSON payload. Replace the payload with a sample in the format your handler produces. For the Hex handler, paste the JSON envelope, for example {"reports":[{"value":"546865726d6f737461742d3031746865726d6f73746174534e2d313233343532352e35"}]}.
Click Test. The Output panel shows deviceName Thermostat-01, deviceType thermostat, serialNumber SN-12345 in attributes, and temperature 25.5 in telemetry. If the output is empty or shows an error, fix the decoder and test again.
Create the Integration
Section titled “Create the Integration”- Go to Integrations ⇾ Integrations and click + Add integration.
- Basic settings:
- Integration type — select UDP.
- Name — keep the default
UDP integrationor enter your own. - Enable integration and Allow create devices or assets — keep both on, so the device is created on its first uplink.
- Click Next.
- Uplink data converter:
- Click Select existing and choose
UDP Uplink Converter. - Alternatively, click Create new to write a decoder here.
- Click Next.
- Click Select existing and choose
- Downlink data converter:
- Click Skip. Add a downlink converter later if you need to send commands to the device.
- Connection:
- Port — keep
11560or set your own. - Handler Type — select the type that matches your payload. See Handler Configuration.
- Copy the Integration key and Integration secret. You need them to start the remote integration.
- Leave the other parameters at their defaults. See Connection Settings for details.
- Port — keep
- Click Add.
Connection Settings
Section titled “Connection Settings”| Parameter | Description |
|---|---|
| Port | UDP port the integration listens on. Default: 11560. Must be free on the remote integration host. |
| Size of the buffer for inbound socket (in KB) | Receive buffer of the UDP socket. Default: 64. Increase it if datagrams are dropped under load. |
| Enable broadcast | Accepts datagrams sent to a broadcast address. On by default. |
| Execute remotely | Always on for UDP. ThingsBoard generates the Integration key and Integration secret for the remote process. |
| Cache Size | Shown only when a downlink converter is assigned. Maximum number of device addresses remembered for downlink delivery. Default: 1000; minimum 1. When the cache is full, older entries are evicted. |
| Cache time to live in minutes | Shown only when a downlink converter is assigned. How long a device address stays valid after its last uplink. Default: 1440 (24 hours); range 1–525600. |
| Metadata | In Advanced settings. Key–value pairs added to the metadata of every message from this integration. |
Handler Configuration
Section titled “Handler Configuration”The handler defines how the integration prepares each datagram for the converter. Each datagram is one message; UDP needs no framing. A datagram of up to 65,507 bytes (the UDP maximum) reaches the converter in full. Select it in Handler Type (default: Binary).
For custom binary protocols. No settings needed.
For CSV lines or other human-readable payloads.
| Parameter | Description |
|---|---|
| Charset Name | Encoding used to decode the bytes, for example UTF-8, ISO-8859-1, or windows-1251. Default: UTF-8. |
For one JSON object per datagram. No settings needed.
For binary payloads that are easier to parse as a hex string. No settings needed.
See Decoder Inputs for the exact payload each handler passes to the converter.
Install and Run the Remote Integration
Section titled “Install and Run the Remote Integration”Install and start the UDP Integration service by following the Remote Integration guide. Configure it with the Integration key and Integration secret from the integration’s connection settings.
Test Uplink
Section titled “Test Uplink”Send a datagram to the integration port, then confirm that ThingsBoard decoded it and created the device.
Send a Test Uplink
Section titled “Send a Test Uplink”Make sure the remote integration process is running. Send a test datagram with nc in UDP mode (-u). Replace localhost with the remote integration host if you send from another machine:
echo 'Thermostat-01,thermostat,SN-12345,25.5' | nc -u -w1 localhost 11560echo -n '{"deviceName": "Thermostat-01", "deviceType": "thermostat", "serialNumber": "SN-12345", "temperature": 25.5}' | nc -u -w1 localhost 11560echo -e -n '\x54\x68\x65\x72\x6d\x6f\x73\x74\x61\x74\x2d\x30\x31\x74\x68\x65\x72\x6d\x6f\x73\x74\x61\x74\x53\x4e\x2d\x31\x32\x33\x34\x35\x32\x35\x2e\x35' | nc -u -w1 localhost 11560echo -n '546865726d6f737461742d3031746865726d6f73746174534e2d313233343532352e35' | xxd -r -p | nc -u -w1 localhost 11560UDP has no acknowledgment, so nc prints nothing on success.
Verify the Result
Section titled “Verify the Result”- Integration events — go to Integrations ⇾ Integrations, open the integration, and click the Events tab. The uplink appears with status OK.
- Converter events — open
UDP Uplink Converterin Integrations ⇾ Data converters and click the Events tab. In shows the payload from the handler, Out shows the decoded result, and Error shows the error text, if any. - Device — go to Devices & assets ⇾ Devices. The integration has created Thermostat-01. Open it and confirm
temperature = 25.5on the Latest telemetry tab andserialNumber = SN-12345on the Attributes tab (Client attributes).
Configure Downlink
Section titled “Configure Downlink”The integration sends downlinks as datagrams to the source IP address and port of the device’s latest uplink. It learns that address only from uplinks, so a device must send at least one uplink first.
- If the device’s address is cached, the downlink is sent immediately, including several downlinks in a row.
- Otherwise, the downlink waits until the next uplink from that device.
- After each downlink payload, the integration sends a second datagram with the line separator of the remote host’s OS (
\non Linux and macOS,\r\non Windows). Device-side parsers must ignore it.
To receive downlinks, the device must listen on the same port it sends from. A one-shot echo ... | nc -u uses a random source port that closes right after sending.
The downlink converter turns a Rule Engine message into the datagram bytes. For the full encoder reference, see Downlink data converter.
Create and Assign the Downlink Converter
Section titled “Create and Assign the Downlink Converter”- Open the integration in Integrations ⇾ Integrations and click Toggle edit mode (pencil icon).
- In the Downlink data converter field, click Create new. The Downlink converter type and UDP integration type are preselected.
- Enter a name, for example
UDP Downlink Converter. - Main encoding configuration opens with a default encoder. Use the encoder below or your own.
- To record downlink events, click the debug icon next to the name, select All messages, and click Apply.
- Click Add. Review Cache Size and Cache time to live in minutes in the connection settings, then click Apply changes.
Encoder used in this guide:
/** Encoder **/
// Encode downlink data from the incoming Rule Engine message.// msg: JSON message payload.// msgType: message type, e.g. 'ATTRIBUTES_UPDATED', 'POST_TELEMETRY_REQUEST', etc.// metadata: key-value pairs containing additional message data.// integrationMetadata: key-value pairs configured in the integration.
var result = { // Specify the response body format: JSON, TEXT, or BINARY. contentType: "JSON",
// Serialize the incoming message and use it as the UDP payload. data: JSON.stringify(msg),
// Add optional metadata when required by the integration. metadata: {}};
return result;The integration sends the encoded data bytes as the datagram.
Configure the Root Rule Chain
Section titled “Configure the Root Rule Chain”To send downlinks through the integration, modify the Root Rule Chain:
- Go to Data processing ⇾ Rule chains and open the Root Rule Chain.
- Find the integration downlink node in the node library panel on the left. Drag it onto the canvas.
- In the node configuration dialog, enter a name (e.g.
Downlink to UDP integration) and select your UDP integration. Click Add. - Connect the message type switch node to the newly created integration downlink node using the Post attributes and Attributes Updated relation types to trigger downlinks whenever shared attributes are created or updated.
- Apply the changes.
Test Downlink
Section titled “Test Downlink”Update a shared attribute to queue a downlink, then send an uplink to receive it.
-
Go to Devices & assets ⇾ Devices, select your device, and open the Attributes tab.
-
Select Shared attributes and click +.
-
Set Key to
temperatureUploadFrequency, Type to Integer, Value to60, and click Add. ThingsBoard queues the downlink. -
Send an uplink and keep the socket open for 10 seconds so
nccan receive the reply:Terminal window echo 'Thermostat-01,thermostat,SN-12345,25.5' | nc -w10 -u localhost 11560
Verify the Downlink
Section titled “Verify the Downlink”- Terminal output —
ncprints the downlink payload:{"temperatureUploadFrequency":60}. - Converter events — open the downlink converter’s Events tab. In shows the Rule Engine message and Out shows the encoded payload sent to the device address.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
| No events in the integration | The remote process is not running, or the datagram did not reach the port. | Check that the remote process is running and connected. Open the UDP port in the firewall. Check the target host and port. |
| Remote process cannot connect to ThingsBoard | Wrong host, port, key, or secret. | Check the connection settings and Integration key / Integration secret in tb-remote-integration.yml. Make sure the gRPC port (9090 by default) is reachable. |
| Port already in use | Another process or integration uses the port. | Pick another Port. The integration retries binding every 5 seconds until the port is free. |
| Decoded values are wrong | The Handler Type does not match the decoder template. | Use the same format for the handler and the template. Capture raw datagrams to compare, for example tcpdump -i any udp port 11560 -X. |
The events show Device name is missing. Provide deviceName in the payload. |
The converter uses the default template, and the message has no deviceName, or it is empty. |
Add deviceName to the message, or adapt the decoder to read the name from another field. |
The events show an error about deviceName or assetName, for example Either 'deviceName' or 'assetName' should be present in the converter output! |
A custom decoder returned no deviceName (for example {}) or an empty one. |
Check In and Error in the converter events. Make sure the decoder extracts deviceName. |
| Converter error | The decoder threw an exception. | Open the converter’s Events tab, read the Error field, and reproduce it with Test decoder function. |
| Telemetry not saved, no error | The decoder returns [] or empty telemetry. |
Make sure telemetry holds at least one key. |
| Wrong timestamp | The device sends ts in seconds; ThingsBoard expects milliseconds. |
Convert it. In the default template: timestamp = data.ts * 1000;. In a custom decoder: ts: data.ts * 1000. |
| Device does not receive downlinks | The device sends from a changing source port, or its cached address expired. | Send and listen on a fixed source port, for example with nc -p. Increase Cache time to live in minutes if needed. |
See Also
Section titled “See Also”- Integrations overview — how integrations, converters, uplinks, and downlinks fit together.
- Uplink data converter — decoder inputs, output format, and scripting patterns.
- Downlink data converter — encoder reference.
- Remote Integration — install and run the integration process.
- TCP Integration — connection-oriented alternative with stream framing.
- TBEL — built-in functions and syntax for converter scripts.
- Rule Engine — how messages are processed after decoding.
Was this helpful?