Skip to content
Stand with Ukraine flag

ChirpStack Integration

The ChirpStack integration connects ThingsBoard to a ChirpStack LoRaWAN Network Server: it receives uplink messages from LoRaWAN devices, decodes them via an uplink converter, and pushes telemetry and attributes to the platform. In the reverse direction, it encodes Rule Engine messages via a downlink converter and delivers them to devices through the ChirpStack API.

To follow this tutorial you need:

  1. Go to Tenant ⇾ API Keys and click Add API key.
  2. Enter a name for the key and click Submit.
  3. Copy and save the token — it is displayed only once.

The uplink converter decodes the incoming ChirpStack message and maps it to the ThingsBoard data model. For the full decoder function reference, see Uplink data converter.

From ThingsBoard 4.0, you can map message fields to attributes or telemetry directly in the UI — no scripting needed for structured JSON payloads. For binary payloads, add a decoder script in the same wizard, as this tutorial demonstrates

If your device is in the built-in catalog, use the Library tab — see Library — a built-in catalog of ready-to-use decoder functions for over 100 devices.

  1. Go to Integrations center ⇾ Data converters.
  2. Click + Add data converter ⇾ Create new converter.
  3. Set Converter type to Uplink (default).
  4. Select Integration type: ChirpStack.
  5. Enter a converter name: ChirpStack Uplink Converter.
  6. The Main decoding parameters section specifies:
    • A naming template for automatic device creation — Device $eui — change this if you prefer a different naming scheme.
    • Leave the decoder function at its default for this tutorial.
  7. Review Advanced decoding parameters — pre-populated for ChirpStack. Adjust if needed; see Advanced decoding parameters for details.

  8. Click Add.
/**
* 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: {}};
// 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);
// 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.
// 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: metadata.ts, // Note: use metadata.ts, not convertDateToTimestamp(metadata.time) — metadata.time may not be populated.
// 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;

Understanding the decoder:

payload is the raw LoRaWAN payload bytes — ThingsBoard decodes the base64 data field automatically. parseBytesToInt(input, offset, length) reads length bytes from offset and returns a big-endian integer — a TBEL built-in; manually defined at the bottom of the JS tab. metadata.ts is the message timestamp in ms, falling back to current server time if unavailable.

The example decoder expects an 8-byte payload with this layout:

BytesLengthExpressionOutput fieldNotes
0–34parseBytesToInt(input, 0, 4)attributes.snSerial number
41parseBytesToInt(input, 4, 1)telemetry.batteryBattery level (0–255)
5–62parseBytesToInt(input, 5, 2) / 100.0telemetry.temperatureTemperature, scaled ÷ 100
71parseBytesToInt(input, 7, 1)telemetry.saturationSaturation (0–255)

Device name and profile come from the Main decoding configuration section. Uncomment result.name, result.profile, etc. to override them from payload content instead.

To adapt this converter to your device:

  • Match your payload structure — update the byte offsets, lengths, and field names in decodePayload to match your device’s binary protocol. Refer to your device’s payload specification.
  • Add or remove fields — add more parseBytesToInt calls (or parseBytesToFloat, string extraction, etc.) for additional sensors; remove entries your device doesn’t transmit.
  • Override device metadata from the payload — uncomment and populate result.name, result.profile, result.group, or result.customer to derive device identity from payload content rather than the converter UI configuration.
  1. Go to Integrations center ⇾ Integrations and click + Add integration.
  2. Basic settings:
    • Set Integration type to ChirpStack.
    • Enable integration and Allow create devices or assets are on by default.
    • Click Next.
  3. Uplink data converter:
    • Select existing — choose the previously created ChirpStack Uplink Converter from the list.
    • Click Next.
  4. Downlink data converter:
    • Click Create new to add a downlink converter inline, or
    • Click Skip — the downlink converter is only needed for sending commands to devices and can be added later.
  5. Connection:
    • Enter the ThingsBoard server Base URL.
    • Copy the HTTP endpoint URL — you will need it when configuring ChirpStack.
    • Enter the Application server URL (the ChirpStack REST API address; with a standard Docker Compose installation, typically port 8090).
    • Enter the Application server API Token — obtained in Create a ChirpStack API key above.
    • Use API for ChirpStack 4+ is enabled by default — disable only when connecting to ChirpStack 3.x.
    Read more about each parameter in Connection settings.
  6. Click Add to complete the integration.

Base URL

The base address of the ThingsBoard server, used to construct the HTTP endpoint URL.

HTTP endpoint URL

Auto-generated endpoint for this integration. Configure ChirpStack to forward uplink messages to this URL.

Application server URL

The address of the ChirpStack REST API service. With a standard Docker Compose installation, this is typically port 8090.

Application server API Token

API token used to authenticate ThingsBoard requests to the ChirpStack API when sending downlink messages. See Create a ChirpStack API key.

Use API for ChirpStack 4+

Enables the ChirpStack v4 API. Enabled by default — disable only when connecting to ChirpStack 3.x.

Execute remotely

When enabled, ThingsBoard generates an Integration key and Integration secret that allow the integration to run as a separate process outside the ThingsBoard cluster — useful when the integration must reach services not accessible from the ThingsBoard server.

To forward uplink messages from ChirpStack to ThingsBoard, add an HTTP integration to your ChirpStack application. If you already have a ChirpStack application with your devices registered, skip steps 1–2.

  1. In the ChirpStack UI, go to Applications and click Add application.
  2. Name the application and click Submit.
  3. Open the application and navigate to the Integrations tab.
  4. In the integration catalog, click + on the HTTP card.
  5. Paste the HTTP endpoint URL copied from the ThingsBoard integration. Click Submit.

When a device sends an uplink message via ChirpStack, ThingsBoard automatically creates the device and stores its telemetry. If you do not have a physical device sending data, you can emulate a device message by sending an HTTP request directly to the ThingsBoard endpoint using curl — bypassing ChirpStack entirely. This is useful to verify that the integration and uplink converter are configured correctly before connecting real hardware. To send a test uplink, you need the HTTP endpoint URL copied from the Connection step of the integration setup.

Use this command to send a message. Replace $HTTP_ENDPOINT_URL with the HTTP endpoint URL copied from the integration.

Terminal window
curl -v \
-H "Content-Type: application/json" \
-d '{"deduplicationId":"7658d04d-7f1c-4eb6-900b-d948f3061a9d","time":"2026-03-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-03-20T14:42:52.653481+00:00","nsTime":"2026-03-20T14:42:52.670509207+00:00","timeSinceGpsEpoch":"1458052990.653s","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"}' \
"$HTTP_ENDPOINT_URL"

Once an uplink is received, a new device named Device 24e124538b223213 (matching the devEui from the payload) appears in Entities ⇾ Devices:

Go to Integrations center ⇾ Integrations, click ChirpStack integration, and open the Events tab to see the uplink event:

To inspect converter processing, go to Integrations center ⇾ Data converters, click the uplink converter, and open its Events tab — In shows the raw ChirpStack message, Out shows the parsed result with deviceName, attributes, and telemetry:

The downlink converter (encoder) transforms a Rule Engine message into the payload delivered to the device through the ChirpStack API. For the full encoder function reference, see Downlink data converter.

The encoder function receives msg, metadata, and msgType, and must return an object with:

  • contentTypeJSON, TEXT, or BINARY (BINARY expects a Base64-encoded string)
  • data — the encoded payload string
  • metadata — key-value pairs forwarded to the integration; DevEUI and fPort are required by the ChirpStack API
  1. Go to Integrations center ⇾ Integrations and open the ChirpStack integration.
  2. Click Toggle edit mode.
  3. In the Downlink data converter field, click Create new.
  4. In the Add data converter dialog, enter a name, and write or paste the encoder script.
  5. Click Add, then click Apply changes.
// Encode downlink data from incoming Rule Engine message
// msg - JSON message payload downlink message json
// msgType - type of message, for ex. 'ATTRIBUTES_UPDATED', 'POST_TELEMETRY_REQUEST', etc.
// metadata - list of key-value pairs with additional data about the message
// integrationMetadata - list of key-value pairs with additional data defined in Integration executing this converter
/** Encoder **/
// Result object with encoded downlink payload
var result = {
// downlink data content type: JSON, TEXT or BINARY (base64 format)
contentType: "TEXT",
// downlink data
data: btoa(msg.downlink),
// Optional metadata object presented in key/value format
metadata: {
DevEUI: metadata.cs_eui,
fPort: metadata.cs_fPort
}
};
return result;

The encoder reads msg.downlink — the value of the downlink shared attribute set on the device — and base64-encodes it as the LoRaWAN payload. metadata.cs_devEui [metadata.cs_eui] and metadata.cs_fPort are populated by the get required fields node in the downlink rule chain, which reads the devEui[eui] and fPort client attributes from the device. Both attributes must be set on each device before downlinks can be delivered.

ChirpStack downlinks require two device-specific values — devEui[eui] and fPort — that must be looked up from the device’s client attributes before calling the ChirpStack API. A dedicated rule chain handles this lookup cleanly. In the Root Rule Chain, a check relation presence node filters messages so that only devices managed by this ChirpStack integration enter the downlink flow, preventing unrelated devices from accidentally triggering it.

Section titled “Import the Downlink to ChirpStack rule chain”

Download the rule chain file: downlink_to_chirpstack.json

  1. Go to Rule chains and click + Add rule chain ⇾ Import rule chain.
  2. Drag the downloaded downlink_to_chirpstack.json file into the import window and click Import.
  3. The Downlink to ChirpStack rule chain opens. Double-click the integration downlink node.
  4. Set Integration to your ChirpStack integration and click Add.
  5. Click Apply changes to save the rule chain.

The rule chain contains two nodes connected in sequence:

  • get required fields — reads the fPort and devEui client attributes from the device and copies them into message metadata as cs_fPort and cs_devEui. If either attribute is absent on the device, the message takes the Failure path and no downlink is sent. Make sure both attributes are set on every device before testing.
  • Send downlink — forwards the message to the ChirpStack integration, which runs the downlink converter and queues the encoded payload through the ChirpStack API.
  1. Go to Rule chains and open the Root Rule Chain.
  2. Search for check relation presence in the node panel and drag it onto the canvas.
  3. Configure the node:
    • Name: Check relation to ChirpStack integration
    • Direction: To originator
    • Relation type: ManagedByIntegration
    • Enable Check relation to specific entity, set Type to Integration, and select your ChirpStack integration.
    • Click Add.
  4. Connect the message type switch node to the check relation presence node using the Attributes Updated link.
  5. Search for rule chain in the node panel and drag it onto the canvas.
  6. Configure the node: Name = Downlink to ChirpStack, Rule chain = Downlink to ChirpStack. Click Add.
  7. Connect the check relation presence node’s True output to the rule chain node.
  8. Click Apply changes to save the Root Rule Chain.

The encoder reads msg.downlink, so the trigger attribute key must be downlink. Adding or updating this shared attribute on the device sends its value as a base64-encoded LoRaWAN payload.

  1. Go to Devices, select your device, open the Attributes tab.
  2. Switch to Shared attributes, click + to add a new attribute.
  3. Enter key downlink and a value (e.g. on), then click Add.

Check the downlink converter Events tab — In shows the ATTRIBUTES_UPDATED message with msg.downlink, and Out shows the encoded payload with DevEUI and fPort in metadata:

Integration not receiving uplink messages

Verify the HTTP endpoint URL is correct. Open the ChirpStack integration in ThingsBoard (Integrations center ⇾ Integrations), click Toggle edit mode, and copy the HTTP endpoint URL from the Connection step. Then open the ChirpStack UI, go to your application’s Integrations tab, and confirm the HTTP integration points to that exact URL.

Verify the ChirpStack HTTP integration is active. In the ChirpStack UI, the HTTP integration must be listed under your application’s Integrations tab. If it is missing, add it following the steps in Add HTTP integration in ChirpStack.

Check the integration Events tab. Go to Integrations center ⇾ Integrations, open the ChirpStack integration, and click the Events tab. If uplinks arrive but show errors, the raw message body is visible there — use it to diagnose converter failures.

Device not created automatically

Check “Allow create devices or assets” is enabled. Open the ChirpStack integration, click Toggle edit mode, and confirm Allow create devices or assets is turned on in the Basic settings step. When this toggle is off, ThingsBoard processes the uplink but does not create a new device if one does not already exist.

Check the uplink converter output. Go to Integrations center ⇾ Data converters, open the uplink converter, and click the Events tab. The Out column must contain a valid deviceName field. If the converter returns an empty object or throws an error, no device is created.

Downlink not delivered to the device

Check the API token. An invalid or expired API token causes ThingsBoard to fail when calling the ChirpStack API. Open the integration, click Toggle edit mode, and re-enter the Application server API Token obtained in Create a ChirpStack API key.

Check DevEUI and fPort in the converter output. Open the downlink converter Events tab and inspect the Out column. The returned metadata object must contain both DevEUI and fPort — the ChirpStack API requires them to queue a downlink. If either is missing, add the devEui and fPort client attributes to the device (the get required fields rule node reads them from there).

Check the rule chain wiring. Open Rule chains ⇾ Root Rule Chain and confirm the check relation presence node is connected to the message type switch node via the Attributes Updated link, and its True output connects to the Downlink to ChirpStack rule chain node. A missing or wrong link silently drops the message before it reaches the downlink converter.

Cannot identify what went wrong

Enable Debug mode on the integration — it captures all raw input/output events and makes them visible on the Events tab. Starting from ThingsBoard 3.9, the full set of debug events is stored only during the first 1 hour; afterward, only error events are retained. Disable debug mode once the issue is identified.