ChirpStack Integration
ChirpStack Integration receives device data from ChirpStack, the open-source LoRaWAN Network Server, through an HTTP integration of a ChirpStack application. An uplink data converter extracts the device payload and the LoRaWAN details from each message and turns them into telemetry and attributes. For downlink, ThingsBoard adds commands to the device queue through the ChirpStack REST API.
Use ChirpStack Integration when:
- Your LoRaWAN devices are registered in ChirpStack, and you want their data in ThingsBoard without registering each device by hand.
- You need to send commands from ThingsBoard to those devices, for example when a shared attribute changes.
Architecture
Section titled “Architecture”- Receive data. The device sends an uplink through a LoRaWAN gateway to ChirpStack. The HTTP integration of the ChirpStack application sends the uplink event as an HTTP request with a JSON body to the integration endpoint.
- Unwrap the event. The uplink converter extracts the device payload from the event, and the device EUI, frame counter, port, radio details, and other fields as metadata.
- Decode the payload. The decoder turns the payload into telemetry and attributes. The converter adds the LoRaWAN details that you select, and builds the device name from the EUI. If Allow create devices or assets is on, ThingsBoard creates the device on its first message.
- Send a command (optional). The Rule Engine passes a message to the integration. The downlink converter encodes it, and ThingsBoard sends it right away to
POST {Application server URL}/api/devices/{DevEUI}/queue. ChirpStack keeps it in the device queue and delivers it in the next receive window of the device: for a Class A device, after its next uplink.
Prerequisites
Section titled “Prerequisites”- Access to ThingsBoard or ThingsBoard Cloud with integrations enabled for your tenant. The ThingsBoard endpoint must be reachable from ChirpStack.
- Permission to create integrations and data converters, and to edit rule chains for downlink.
- A ChirpStack v4 server. To run one locally, see Start ChirpStack.
- A LoRaWAN gateway and a device registered in ChirpStack. See the ChirpStack device connection guide. To test the uplink without hardware, you can send a test uplink with curl.
- For downlink: the ChirpStack REST API must be reachable from ThingsBoard. If ChirpStack runs in a private network and ThingsBoard cannot reach it, run the integration as a Remote Integration.
Prepare ChirpStack
Section titled “Prepare ChirpStack”In ChirpStack, devices belong to an application, and the application’s integrations forward the device events. Here you start ChirpStack if you do not have a server yet, and create the application. The HTTP integration comes later: it needs the integration endpoint, so you add it after creating the integration.
Start ChirpStack
Section titled “Start ChirpStack”Skip this step if you already have a ChirpStack server. The chirpstack-docker repository runs ChirpStack with Docker Compose.
-
Install Docker and Docker Compose. See the Docker installation guide.
-
Clone the repository:
Terminal window git clone https://github.com/chirpstack/chirpstack-docker.gitcd chirpstack-docker -
Both gateway bridges in this setup use the EU868 region. To use another region, change the topic prefix as described in the
README.mdof the repository. -
Start ChirpStack:
Terminal window docker compose upOn the first run, the logs can show errors while the database initializes. This is expected.
-
Point your gateway to the host. The ChirpStack Gateway Bridge accepts Semtech UDP Packet Forwarder traffic on UDP port
1700, and LoRa Basics Station connections on port3001. -
Open
http://localhost:8080(orhttp://localhost:8081if you changed the port) and sign in with the usernameadminand the passwordadmin. If ChirpStack runs on another host, replacelocalhostwith its address. The REST API that ThingsBoard uses for downlink listens on port8090.
Create an Application
Section titled “Create an Application”Skip this step if your devices are already in an application.
- In the ChirpStack UI, go to Tenant ⇾ Applications and click Add application.
- Enter a Name, for example
ThingsBoard, and click Submit. - Open the application and add your devices on the Devices tab. See the ChirpStack device connection guide.
Set Up ChirpStack Integration
Section titled “Set Up ChirpStack Integration”Create the uplink converter and the integration, then point a ChirpStack HTTP integration at the integration endpoint.
Create the Uplink Data Converter
Section titled “Create the Uplink Data Converter”ChirpStack Integration uses the Typed uplink converter. It unwraps the ChirpStack event before your decoder runs: the decoder gets only the device payload as a byte array, and the event fields arrive as metadata. The device name, profile, label, and the LoRaWAN details to store are set in the converter form. The script can override them. ThingsBoard selects this converter for the ChirpStack integration type, and you cannot change it later. See Typed uplink converter.
If your device model is in the catalog, you can take a ready-made decoder instead. See Use a Library Converter.
-
Go to Integrations ⇾ Data converters and click + Add data converter ⇾ Create new converter.
-
Converter type — keep Uplink.
-
Integration type — select ChirpStack.
-
Keep Create new selected and enter a Name, for example
ChirpStack Uplink Converter. -
Main decoding configuration:
- Entity type — keep Device.
- Device name — keep
Device $eui.$euiis replaced with the device EUI, so each device gets a unique name, for exampleDevice 24e124538b223213. Any key from Decoder Inputs works as$key. - The editor shows the body of
function payloadDecoder(payload, metadata)with the default ChirpStack decoder below. It reads an 8-byte example payload. Replace the byte layout with the one of your device, or keep it for this guide. Use the TBEL / JS toggle to switch to JavaScript.
/*** 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: {}};// 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.if (metadata.payloadFormat == 'JSON') {// payloadFormat is 'JSON' when ChirpStack sends a pre-decoded JSON payload// (the uplink contains an "object" field produced by a codec/payload formatter).// All fields from the decoded object are put directly into telemetry.var decoded = decodeToJson(input);result.telemetry = {ts: timestamp,values: decoded};return result;}// payloadFormat is 'BINARY' when raw base64 payload is received (data field).// 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(input, 0, 4);// 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(input, 4, 1),// Decode temperature from the 6th and 7th bytes of the payload (divided by 100).temperature: parseBytesToInt(input, 5, 2) / 100.0,// Decode saturation from the 8th byte of the payload.saturation: parseBytesToInt(input, 7, 1)}};// Return the fully constructed output object.return result;// Same logic, less code:// return {// attributes: {// sn: parseBytesToInt(input, 0, 4)// },// telemetry: {// ts: convertDateToTimestamp(metadata.time),// values: {// battery: parseBytesToInt(input, 4, 1),// temperature: parseBytesToInt(input, 5, 2) / 100.0,// saturation: parseBytesToInt(input, 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;/*** Decodes the incoming payload and returns a structured object containing telemetry data and attributes.** @param {number[]} 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: {}};// 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.if (metadata.payloadFormat == 'JSON') {// payloadFormat is 'JSON' when ChirpStack sends a pre-decoded JSON payload// (the uplink contains an "object" field produced by a codec/payload formatter).// All fields from the decoded object are put directly into telemetry.var decoded = decodeToJson(input);result.telemetry = {ts: timestamp,values: decoded};return result;}// payloadFormat is 'BINARY' when raw base64 payload is received (data field).// 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(input, 0, 4);// Initialize an object to store decoded key/value telemetry data.var values = {};// Decode battery level from the 5th byte of the payload.values.battery = parseBytesToInt(input, 4, 1);// Decode temperature from the 6th and 7th bytes of the payload (divided by 100).values.temperature = parseBytesToInt(input, 5, 2) / 100.0;// Decode saturation from the 8th byte of the payload.values.saturation = parseBytesToInt(input, 7, 1);// Combine the timestamp with values and add it to the telemetry.result.telemetry = {ts: timestamp,values: values};// Return the fully constructed output object.return result;// Same logic, less code:// return {// attributes: {// sn: parseBytesToInt(input, 0, 4)// },// telemetry: {// ts: convertDateToTimestamp(metadata.time),// values: {// battery: parseBytesToInt(input, 4, 1),// temperature: parseBytesToInt(input, 5, 2) / 100.0,// saturation: parseBytesToInt(input, 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;/*** Converts a byte array to a string and parses it as JSON.** @param {number[]} payload - The array of bytes.* @returns {Object} - The parsed JSON object.*/function decodeToJson(payload) {return JSON.parse(String.fromCharCode.apply(String, payload));}/*** Converts a slice of bytes from an array into an integer (big-endian).** @param {number[]} input - The array of bytes.* @param {number} offset - The starting index.* @param {number} length - The number of bytes to convert.* @returns {number} - The resulting integer.*/function parseBytesToInt(input, offset, length) {var result = 0;for (var i = offset; i < offset + length; i++) {result = (result << 8) | (input[i] & 0xFF);}return result;}The decoder has two paths, chosen by
metadata.payloadFormat:payloadFormatWhen payloadholdsThe decoder BINARYNo codec on the ChirpStack device profile The bytes of the Base64 datafieldReads the example layout below JSONA codec on the ChirpStack device profile decoded the payload The objectfield as JSON textStores every field of objectas telemetryThe example layout of the
BINARYpath:Bytes Expression Result 0–3 parseBytesToInt(input, 0, 4)Attribute sn4 parseBytesToInt(input, 4, 1)Telemetry battery5–6 parseBytesToInt(input, 5, 2) / 100.0Telemetry temperature7 parseBytesToInt(input, 7, 1)Telemetry saturationTo adapt:
- Payload layout — change the offsets, lengths, and keys in
decodePayloadto match the payload format of your device. If a ChirpStack codec already decodes the payload, theJSONpath needs no changes. - Device identity — set
result.name,result.profile,result.customer, orresult.groupin the script to override the form fields, as the commented lines show.
-
Advanced decoding parameters — keep the defaults:
- Device profile name —
$deviceProfileName, the ChirpStack device profile name. ThingsBoard assigns the device profile with this name and creates it if it does not exist. - Device label —
$deviceName, the device name in ChirpStack. - Customer name and Device group name — empty. Set them to assign new devices to a customer or add them to a group.
- Telemetry —
fCnt,data,decoded,latitude,longitude,rssi,snr,channel,altitude. - Attributes —
eui,devAddr,fPort,tenantId,tenantName,applicationId,applicationName,adr,dr,frequency,bandwidth,spreadingFactor,codeRate. - Update only keys list — the same keys as Attributes. These keys are stored only when their value changes.
The Telemetry and Attributes lists do not filter the decoder output. They add the listed keys from the event metadata to the telemetry and attributes that the decoder returns. A key that the event does not carry is skipped. Downlink in this guide needs the
euiandfPortattributes, so keep them in the list. - Device profile name —
-
Click Test payload decoder and check the result (see Test the Decoder).
-
Click Add.
Decoder Inputs
| Variable | Type | Content |
|---|---|---|
payload |
byte array | The device payload: the decoded Base64 data field, or the object field as JSON text when a ChirpStack codec decoded the payload. Empty if the event carries neither. |
metadata |
{[key: string]: any} |
The fields extracted from the ChirpStack event, listed below, plus integrationName and the key–value pairs from the integration’s Metadata field. A Metadata key with the same name as an extracted field replaces it. |
ChirpStack sends an uplink event like this:
{ "deduplicationId": "7658d04d-7f1c-4eb6-900b-d948f3061a9d", "time": "2026-10-20T14:42:52.653+00:00", "deviceInfo": { "tenantId": "6e073e9a-6b4f-4747-b22c-7507e0fdebfb", "tenantName": "ChirpStack", "applicationId": "c3f2c4aa-07c8-4d6c-8a86-7ea2d4a52dca", "applicationName": "Sample Application", "deviceProfileId": "d8ee6c09-414c-4b2e-888a-8e8f86e2197a", "deviceProfileName": "chirpstack-sensor", "deviceName": "chirpstack sensor 001", "devEui": "24e124538b223213", "deviceClassEnabled": "CLASS_A", "tags": {} }, "devAddr": "01a44d4c", "adr": true, "dr": 5, "fCnt": 153, "fPort": 84, "confirmed": false, "data": "ALxhTl8JKVA=", "rxInfo": [{ "gatewayId": "24e124fffef64e9e", "uplinkId": 25127, "gwTime": "2026-10-20T14:42:52.653481+00:00", "nsTime": "2026-10-20T14:42:52.670509207+00:00", "rssi": -68, "snr": 13.2, "channel": 4, "location": {}, "context": "Hw9+zQ==", "crcStatus": "CRC_OK" }], "txInfo": { "frequency": 867300000, "modulation": { "lora": { "bandwidth": 125000, "spreadingFactor": 7, "codeRate": "CR_4_5" } } }, "regionConfigId": "eu868"}The converter extracts these metadata keys from it:
| Key | Source in the event | Example |
|---|---|---|
eui |
deviceInfo.devEui |
24e124538b223213 |
deviceName, deviceProfileName, deviceProfileId |
deviceInfo |
chirpstack sensor 001, chirpstack-sensor |
tenantId, tenantName, applicationId, applicationName, tags |
deviceInfo |
ChirpStack, Sample Application |
deduplicationId, devAddr, adr, dr, fCnt, fPort, confirmed |
Top level | 01a44d4c, true, 5, 153, 84, false |
data |
data, the Base64 payload |
ALxhTl8JKVA= |
decoded |
object, if a ChirpStack codec decoded the payload |
— |
rxInfo |
rxInfo, all gateways that received the uplink |
— |
rssi, snr |
rxInfo entry with the highest rssi |
-68, 13.2 |
frequency, bandwidth, spreadingFactor, codeRate |
txInfo |
867300000, 125000, 7, CR_4_5 |
latitude, longitude, altitude |
location of a ChirpStack location event, which carries the device geolocation. Gateway locations in rxInfo of uplink events are not used. |
— |
time |
time |
2026-10-20T14:42:52.653+00:00 |
ts, timeTs |
time in Unix milliseconds. If time is missing or cannot be parsed, ts is the current time. |
1792507372653 |
payloadFormat |
JSON if the event has object, otherwise BINARY |
BINARY |
Test the Decoder
Click Test payload decoder below the editor. The dialog shows the last message that the converter received, or a ChirpStack sample event, already split into Payload and Metadata. To test with the event from Decoder Inputs:
- Click Updated RAW payload.
- Replace Payload with the event. Keep Metadata: it holds the integration metadata, such as
integrationName. Click Apply. - Payload and Metadata now show the unwrapped event. Click Test.
The Decoder output tab shows what the decoder returned. For the payload ALxhTl8JKVA=, the bytes 00 BC 61 4E 5F 09 29 50 give:
{ "attributes": { "sn": 12345678 }, "telemetry": { "ts": 1792507372653, "values": { "battery": 95, "temperature": 23.45, "saturation": 80 } }}The Converter output tab shows the result after the form fields are applied. The name, profile, and label come from Device name, Device profile name, and Device label. The metadata keys from Telemetry and Attributes that the event carries are added to the decoder output:
{ "entityType": "DEVICE", "name": "Device 24e124538b223213", "profile": "chirpstack-sensor", "label": "chirpstack sensor 001", "telemetry": { "ts": 1792507372653, "values": { "battery": 95, "temperature": 23.45, "saturation": 80, "data": "ALxhTl8JKVA=", "fCnt": 153, "rssi": -68, "snr": 13.2 } }, "attributes": { "sn": 12345678, "devAddr": "01a44d4c", "fPort": 84, "dr": 5, "frequency": 867300000, "tenantName": "ChirpStack", "applicationName": "Sample Application", "bandwidth": 125000, "adr": true, "codeRate": "CR_4_5", "spreadingFactor": 7, "eui": "24e124538b223213", "tenantId": "6e073e9a-6b4f-4747-b22c-7507e0fdebfb", "applicationId": "c3f2c4aa-07c8-4d6c-8a86-7ea2d4a52dca" }}If the output is empty or shows an error, fix the decoder and test again.
Use a Library Converter
The Converters library holds ready-made converters for devices from vendors such as Milesight, Elsys, Decentlab, and Bosch. They are maintained in the data-converters repository.
- In the Add data converter dialog, select Uplink and the ChirpStack integration type, then switch from Create new to Library.
- Vendor — select the manufacturer, for example Milesight.
- Model — select the device model, for example AM102L: 2-in-1 Environment Sensor.
- Name is filled in as
ChirpStack Uplink Converter for <Vendor> <Model>, for exampleChirpStack Uplink Converter for Milesight AM102L. Change it if needed. - Review the decoder and the Advanced decoding parameters, and click Add. The TBEL / JS toggle is locked to the language of the entry.
A library converter can work differently from the converter above. Some entries, such as Milesight AM102L, are written as Generic converters: the function is decoder(payload, metadata), payload is the whole ChirpStack event, and the decoder reads the device name and the payload from it, for example var data = decodeToJson(payload); and data.deviceInfo.deviceName. Such a converter shows only the Update only keys list under Advanced decoding parameters. Its test button is Test decoder function, and the test dialog takes the whole event as a Json payload. The AM102L entry names devices AM102L <ChirpStack device name> and adds rssi, snr, channel, rfChain, and fCnt to the telemetry only if the integration Metadata has includeGatewayInfo = true. Check the decoder of the entry you select before you rely on the device name or keys described in this guide.
Create the Integration
Section titled “Create the Integration”- Go to Integrations ⇾ Integrations and click + Add integration.
- Basic settings:
- Integration type — select ChirpStack.
- Name — keep the default
ChirpStack integrationor enter your own. - Enable integration and Allow create devices or assets — keep both on, so the device is created on its first message.
- Click Next.
- Uplink data converter:
- Click Select existing and choose
ChirpStack Uplink Converter. - Alternatively, click Create new to configure the converter here, or Library to take one from the catalog.
- Click Next.
- Click Select existing and choose
- Downlink data converter:
- Click Skip. Add a downlink converter later if you need to send commands.
- Connection:
- Copy the HTTP endpoint URL. You need it for the ChirpStack HTTP integration.
- Leave Allow downlink off for now, and the other parameters at their defaults. See Connection Settings for details.
- Click Add.
Connection Settings
Section titled “Connection Settings”| Parameter | Default | Description |
|---|---|---|
| Base URL | Address you signed in to | Used to build the HTTP endpoint URL. Required. Use an address at which ChirpStack can reach ThingsBoard, for example http://my.thingsboard:8080. |
| HTTP endpoint URL | — | Read-only. Base URL, then /api/v1/integrations/chirpstack/, then the integration key, a UUID assigned at creation. Accepts JSON bodies only. |
| Allow downlink | Off | Shows the Application server URL and Application server API Token fields. Turning it off clears both. See Configure Downlink. |
| Application server URL | — | In Allow downlink. Address of the ChirpStack REST API, without a path, for example http://my.chirpstack:8090. ThingsBoard adds /api/devices/{DevEUI}/queue. Required when Allow downlink is on. |
| Application server API Token | — | In Allow downlink. A ChirpStack API key, sent as a Bearer token. Click the key icon to take it from Secrets storage. Required when Allow downlink is on. |
| Use API for ChirpStack 4+ | On | Sends downlinks in the ChirpStack v4 format, with the queueItem body. Turn it off only for ChirpStack v3, which expects deviceQueueItem. |
| Execute remotely | Off | Runs the integration as a separate process outside the ThingsBoard cluster. ThingsBoard generates an Integration key and Integration secret for it. Use it when ChirpStack and ThingsBoard cannot reach each other directly, for example when ChirpStack runs in a private network. See Remote Integration. |
| Description | — | In Advanced settings. Free-text description. |
| Metadata | — | In Advanced settings. Key–value pairs added to the metadata of every message from this integration. |
The integration answers 200 OK when the message was processed, and an error status, such as 500 Internal Server Error, when the converter or the processing fails. It has no request check: anyone who knows the endpoint URL can send data to it.
Add the HTTP Integration in ChirpStack
Section titled “Add the HTTP Integration in ChirpStack”- In the ChirpStack UI, go to Tenant ⇾ Applications and open your application.
- Open the Integrations tab and click + on the HTTP card. Do not use the ThingsBoard card: it sends data straight to the device API with a separate access token per device, bypassing ChirpStack Integration and its converters.
- Payload encoding — keep JSON. ThingsBoard does not accept Protobuf.
- Event endpoint URL(s) — paste the HTTP endpoint URL of the integration. If ChirpStack reaches ThingsBoard at another address, for example when both run on one host and ChirpStack runs in Docker, replace the host part of the URL with that address.
- Click Submit.
ChirpStack sends every device event of the application to this URL, not only uplinks. ThingsBoard processes each of them as an uplink.
Test Uplink
Section titled “Test Uplink”Send an uplink, then confirm that ThingsBoard decoded it and created the device.
Send a Test Uplink
Section titled “Send a Test Uplink”Wait for the next uplink of your device. ChirpStack forwards it to the endpoint.
To test without a device, send the event from Decoder Inputs to the endpoint with curl. Replace $HTTP_ENDPOINT_URL with the HTTP endpoint URL and run:
curl -v -X POST "$HTTP_ENDPOINT_URL" \ -H "Content-Type: application/json" \ -d '{"deduplicationId":"7658d04d-7f1c-4eb6-900b-d948f3061a9d","time":"2026-10-20T14:42:52.653+00:00","deviceInfo":{"tenantId":"6e073e9a-6b4f-4747-b22c-7507e0fdebfb","tenantName":"ChirpStack","applicationId":"c3f2c4aa-07c8-4d6c-8a86-7ea2d4a52dca","applicationName":"Sample Application","deviceProfileId":"d8ee6c09-414c-4b2e-888a-8e8f86e2197a","deviceProfileName":"chirpstack-sensor","deviceName":"chirpstack sensor 001","devEui":"24e124538b223213","deviceClassEnabled":"CLASS_A","tags":{}},"devAddr":"01a44d4c","adr":true,"dr":5,"fCnt":153,"fPort":84,"confirmed":false,"data":"ALxhTl8JKVA=","rxInfo":[{"gatewayId":"24e124fffef64e9e","uplinkId":25127,"gwTime":"2026-10-20T14:42:52.653481+00:00","nsTime":"2026-10-20T14:42:52.670509207+00:00","rssi":-68,"snr":13.2,"channel":4,"location":{},"context":"Hw9+zQ==","crcStatus":"CRC_OK"}],"txInfo":{"frequency":867300000,"modulation":{"lora":{"bandwidth":125000,"spreadingFactor":7,"codeRate":"CR_4_5"}}},"regionConfigId":"eu868"}'The endpoint returns 200. ThingsBoard stores the telemetry with the time in time. Change it to the current time to see the data as the latest values.
Verify the Result
Section titled “Verify the Result”-
Integration events — go to Integrations ⇾ Integrations, open the integration, and click the Events tab. The message appears as an event of type Uplink with status OK. Click
⋅⋅⋅in the Message column to see the received event. -
Converter events — open
ChirpStack Uplink Converterin Integrations ⇾ Data converters and click the Events tab. In shows the whole ChirpStack event, before the converter unwraps it. Out shows what the decoder returned, without the keys added from Telemetry and Attributes. Metadata showsintegrationNameand the integration’s Metadata pairs, not the extracted keys. -
Device — go to Devices & assets ⇾ Devices. The integration has created the device
Device 24e124538b223213. Its profile and label are the device profile name and device name from ChirpStack, for the example eventchirpstack-sensorandchirpstack sensor 001. Open it and check:- Latest telemetry —
battery,temperature, andsaturationfrom the decoder, andfCnt,data,rssi, andsnrfrom the event. - Attributes — Client attributes
snfrom the decoder, andeui,devAddr,fPort,tenantId,tenantName,applicationId,applicationName,adr,dr,frequency,bandwidth,spreadingFactor, andcodeRatefrom the event.
Keys in Update only keys list are saved only when their value changes, so their update time can stay at the first uplink.
- Latest telemetry —
Configure Downlink
Section titled “Configure Downlink”ThingsBoard sends each downlink right away as a ChirpStack REST API call. Nothing is queued in ThingsBoard: if the call fails, ThingsBoard records an error and drops the command. ChirpStack adds an accepted downlink to the device queue and sends it in the next receive window of the device: for a Class A device, after its next uplink.
For each downlink, the integration sends POST {Application server URL}/api/devices/{DevEUI}/queue with the API key as a Bearer token and this body:
{ "queueItem": { "confirmed": false, "data": "eyJ0ZW1wZXJhdHVyZVVwbG9hZEZyZXF1ZW5jeSI6NjB9", "fPort": 84 }}DevEUI, fPort, and confirmed come from the metadata of the encoder result, and data from its data. With Use API for ChirpStack 4+ off, the body key is deviceQueueItem. For the full encoder reference, see Downlink data converter.
Create a ChirpStack API Key
Section titled “Create a ChirpStack API Key”The integration authenticates to the ChirpStack REST API with a tenant API key.
- In the ChirpStack UI, go to Tenant ⇾ API Keys and click Add API key.
- Enter a Name, for example
ThingsBoard. Keep Read only off: ThingsBoard adds downlinks to the device queue. Click Submit. - Copy the token. ChirpStack shows it only once.
Create and Assign the Downlink Converter
Section titled “Create and Assign the Downlink Converter”Add a downlink converter to the integration and turn on Allow downlink with the API key.
- 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 ChirpStack integration type are preselected.
- Enter a name, for example
ChirpStack Downlink Converter. - Main encoding configuration opens in TBEL with a generic encoder. It builds a JSON object from
temperatureUploadFrequencyandhumidityUploadFrequencyand sets an MQTTtopic, not theDevEUIandfPortthat ChirpStack needs. Replace it with the encoder below. For JavaScript, use the TBEL / JS toggle and the JavaScript tab. - To record downlink events, click the debug button next to the name, select All messages, and click Apply.
- Click Add.
- Enable downlink: turn on Allow downlink. Enter the Application server URL and paste the token into Application server API Token.
- Click Apply changes.
Encoder used in this guide. It sends the message as compact JSON text, for example {"temperatureUploadFrequency":60}, encoded in Base64 as ChirpStack expects. It reads the device EUI and port from the cs_eui and cs_fPort metadata keys, which the rule chain adds from the device’s client attributes.
/** Encoder **/
// Device EUI and LoRaWAN port. The rule chain adds the eui and fPort// client attributes to the metadata as cs_eui and cs_fPort.var devEui = metadata['cs_eui'];var fPort = metadata['cs_fPort'];
return { // TEXT passes the Base64 string to ChirpStack unchanged. contentType: "TEXT", // ChirpStack expects the device payload as a Base64 string. data: btoa(JSON.stringify(msg)), metadata: { // Required. The device to send the downlink to. DevEUI: devEui, // Required. An integer from 1 to 223 that your device listens on. fPort: fPort, // Optional. "true" asks the device to acknowledge the downlink. confirmed: "false" }};/** Encoder **/
// Device EUI and LoRaWAN port. The rule chain adds the eui and fPort// client attributes to the metadata as cs_eui and cs_fPort.var devEui = metadata['cs_eui'];var fPort = metadata['cs_fPort'];
return { // TEXT passes the Base64 string to ChirpStack unchanged. contentType: "TEXT", // ChirpStack expects the device payload as a Base64 string. data: btoa(JSON.stringify(msg)), metadata: { // Required. The device to send the downlink to. DevEUI: devEui, // Required. An integer from 1 to 223 that your device listens on. fPort: fPort, // Optional. "true" asks the device to acknowledge the downlink. confirmed: "false" }};To check the encoder, click Test encoder function. Replace the Message with {"temperatureUploadFrequency":60}, add cs_eui = 24e124538b223213 and cs_fPort = 84 to Metadata, and click Test. The Output panel shows:
{ "contentType": "TEXT", "data": "eyJ0ZW1wZXJhdHVyZVVwbG9hZEZyZXF1ZW5jeSI6NjB9", "metadata": { "DevEUI": "24e124538b223213", "fPort": "84", "confirmed": "false" }}The integration reads these fields from the encoder result:
| Field | Required | Description |
|---|---|---|
data |
Yes | The device payload as a Base64 string. The integration sends it to ChirpStack as is. |
contentType |
Yes | Use TEXT or JSON. |
metadata.DevEUI |
Yes | Device EUI. Without it, the downlink fails with DevEUI is missing in the downlink metadata!. |
metadata.fPort |
Yes | LoRaWAN port, as an integer or a numeric string. Without it, the downlink fails with FPort is missing in the downlink metadata!. |
metadata.confirmed |
No | true or false. Any other value fails the downlink. |
The encoder sends the whole message. A LoRaWAN downlink carries from about 50 to about 220 bytes, depending on the region and data rate. To send a compact binary command instead, build a byte array, encode it with bytesToBase64() in TBEL, and return it as data. To send several downlinks from one message, return an array of results.
Configure the Root Rule Chain
Section titled “Configure the Root Rule Chain”The encoder needs the device EUI and port. Add them to the metadata with an originator attributes node, then pass the message to the integration. A check relation presence node in front lets through only devices that this integration created.
- Go to Data processing ⇾ Rule chains and open the Root Rule Chain.
- From the Filter section, drag a check relation presence node onto the canvas:
- Name — for example
Managed by ChirpStack. - Direction — To originator.
- Relation type —
ManagedByIntegration. - Turn on Check relation to specific entity, set Type to Integration, and select
ChirpStack integration. - Click Add.
- Name — for example
- From the Enrichment section, drag an originator attributes node onto the canvas:
- Name — for example
Get EUI and port. - Client attributes — add
euiandfPort. - Add originator attributes to — select Metadata.
- Turn on Tell failure if any of the attributes are missing.
- Click Add.
- Name — for example
- From the Action section, drag an integration downlink node onto the canvas:
- Name — for example
Downlink to ChirpStack. - Integration — select
ChirpStack integration. - Click Add.
- Name — for example
- Connect the message type switch node to
Managed by ChirpStackwith the Attributes Updated relation. - Connect
Managed by ChirpStacktoGet EUI and portwith the True relation. - Connect
Get EUI and porttoDownlink to ChirpStackwith the Success relation. - Click Apply changes.
Test Downlink
Section titled “Test Downlink”ChirpStack accepts a downlink only for a device that exists in ChirpStack with the same DevEUI. If you created the ThingsBoard device with curl, register a device with the DevEUI 24e124538b223213 in ChirpStack first, or use a device that sent a real uplink.
- Go to Devices & assets ⇾ Devices and open the device created in Test Uplink.
- On the Attributes tab, select Shared attributes and click +.
- Enter Key
temperatureUploadFrequency, select the Integer type, enter Value60, and click Add.
Verify the Downlink
Section titled “Verify the Downlink”- Converter events — open
ChirpStack Downlink Converterin Integrations ⇾ Data converters and click the Events tab. In shows the message{"temperatureUploadFrequency":60}withcs_euiandcs_fPortin its metadata. Out shows the Base64data,DevEUI, andfPort. - Integration events — open the integration’s Events tab. Two events of status OK appear: Downlink: ATTRIBUTES_UPDATED for the message from the Rule Engine, and Downlink for the call to ChirpStack.
- ChirpStack — open the device in the ChirpStack application and go to the Queue tab. The downlink is listed with FPort
84until ChirpStack sends it after the next uplink of the device.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
| No events on the integration Events tab | The HTTP integration in ChirpStack is missing or has a wrong URL, ChirpStack cannot reach ThingsBoard, or the integration is disabled. Or debug mode is off: it records events for 15 minutes after you turn it on. | Check the Integrations tab of the ChirpStack application. Copy the HTTP endpoint URL again. Turn on Enable integration. Turn debug mode on again. |
No events on the integration Events tab, and the ChirpStack log shows Posting event failed |
Event endpoint URL(s) points to an address at which ChirpStack does not reach ThingsBoard, for example localhost when ChirpStack runs in Docker. |
Use an address that ChirpStack can reach, such as the host address of the Docker network. Check the log again after the next uplink. |
| Downlink stays on the Queue tab after the next uplink | ChirpStack first sends pending MAC commands to the device, for example right after activation. | Wait for the next uplinks. The downlink is sent once the device has answered the MAC commands. |
No events on the integration Events tab, and the ChirpStack log shows Posting event failed with 415 |
Payload encoding in ChirpStack is Protobuf. The integration accepts only JSON. | Set it to JSON. |
URL must be in the public network. under Base URL |
On ThingsBoard Cloud, Base URL must be a public address. | Keep the default Base URL. To send data from a private network, use a Remote Integration. |
| Uplink events with status ERROR | The decoder failed, for example because the payload is shorter than the byte layout expects. | Check the Error column in the converter events and fix the decoder. |
| Device not created | Allow create devices or assets is off. | Turn it on in the integration’s Basic settings. |
Telemetry key decoded with a JSON object |
A ChirpStack codec decoded the payload. The decoder stores its fields as telemetry, and the default Telemetry list adds the whole object as decoded. |
Remove decoded from Telemetry if you do not need it. |
| No downlink events at all | The rule chain does not route attribute updates, or the device was not created by this integration, so check relation presence returns False. | Check the Attributes Updated and True relations in the Root Rule Chain. |
Message stops at Get EUI and port with Failure |
The device has no eui or fPort client attribute. |
Keep eui and fPort in the converter’s Attributes list, and send an uplink to store them. |
DevEUI is missing in the downlink metadata! or FPort is missing in the downlink metadata! |
The encoder does not set metadata.DevEUI or metadata.fPort. The generic default encoder sets neither. |
Use the encoder from this guide. |
ChirpStack rejected downlink (401 ...) |
The API key is wrong or was deleted, or no device with this DevEUI exists in the ChirpStack tenant of the key. | Check the key in Application server API Token, or create a new one. Register the device in ChirpStack. |
ChirpStack rejected downlink (400 ...), for example invalid value for bytes field data |
data is not a Base64 string, or the body format does not match the ChirpStack version. |
Return data as a Base64 string, as the encoder in this guide does. Turn Use API for ChirpStack 4+ on for ChirpStack v4 and off for v3. |
I/O error on POST request for "...", for example Connection refused |
ThingsBoard cannot reach the Application server URL. | Check the address and port. If ChirpStack is in a private network, use a Remote Integration. |
To read debug events, open the integration or converter, click Events, and select an event: In is the received data, Out is the converter result, and Error holds the error text.
See Also
Section titled “See Also”- Integrations overview — how integrations, converters, uplinks, and downlinks fit together.
- Uplink data converter — typed and generic converters, the converters library, and the output format.
- Downlink data converter — encoder reference.
- message type switch node — route messages by type, for example attribute updates.
- check relation presence node — route messages by a relation of the originator.
- originator attributes node — add attributes of the message originator to the metadata.
- integration downlink node — route Rule Engine messages to an integration.
- Remote Integration — run the integration outside the ThingsBoard cluster.
- TBEL — built-in functions and syntax for converter scripts.
- Secrets storage — store the ChirpStack API key as a secret.
- ChirpStack HTTP integration — event types and payload encoding.
- ChirpStack event types — the fields of uplink and other events.
- ChirpStack REST API — the REST interface that the integration calls for downlinks.
Was this helpful?