Particle Integration
Particle Integration receives events from Particle Device Cloud through a webhook. An uplink data converter transforms each event into device data, including telemetry and attributes. For downlink, ThingsBoard calls a cloud function on the device through the Particle Cloud API.
Use Particle Integration when:
- Your devices run Particle Device OS and publish events with
Particle.publish(). - You need to call device functions (
Particle.function()) from ThingsBoard, for example from a dashboard control.
Architecture
Section titled “Architecture”- Publish an event. The device firmware calls
Particle.publish(). - Forward the event. A Particle webhook that matches the event name sends it as an HTTP
POSTto the integration endpoint. The body holds the event name, data, publish time, and Device ID (coreid). - Decode the payload. The uplink converter maps
coreidto the device name and the event data to telemetry and attributes. If Allow create devices or assets is on, ThingsBoard creates the device on its first event. - Call a device function (optional). The Rule Engine passes an RPC request to the integration. The downlink converter encodes it, and ThingsBoard calls
POST https://api.particle.io/v1/devices/{deviceId}/{function}, which runs the matchingParticle.function()on the device.
Prerequisites
Section titled “Prerequisites”- Access to ThingsBoard Cloud with integrations enabled for your tenant. The ThingsBoard endpoint must be reachable from the internet, because Particle Device Cloud calls it.
- Permission to create integrations and data converters, and to edit rule chains for downlink.
- A Particle Console account with at least one device that connects to Particle Device Cloud.
- For downlink: the Particle CLI to create an access token.
Prepare Particle
Section titled “Prepare Particle”Register the device and flash firmware that publishes a ThingsBoard event.
Set Up a Particle Device
Section titled “Set Up a Particle Device”For a single development device, use Particle Device Setup. For a product fleet, use your existing provisioning and claiming process.
- Power the device and complete network setup. Wi-Fi devices can use setup.particle.io.
- Register or claim the device in your sandbox or product.
- Open Particle Console ⇾ Devices and check that the device is listed and online.
- Copy the Device ID. Webhooks send it as
coreid, and this guide uses it as the ThingsBoard device name.
Publish Events from Firmware
Section titled “Publish Events from Firmware”This guide publishes the event tb/telemetry. Webhooks match event names by prefix, so a webhook for tb/ also receives later events such as tb/status or tb/location.
Event names may contain letters, numbers, underscores, dashes, and slashes. Names that start with particle or spark are reserved.
The firmware below publishes example values every 30 seconds. Replace them with real sensor data.
#include "Particle.h"
SYSTEM_THREAD(ENABLED);
unsigned long lastPublishMs = 0;const std::chrono::milliseconds publishPeriod = 30s;
void setup() {}
void loop() { if (Particle.connected() && millis() - lastPublishMs >= publishPeriod.count()) {
lastPublishMs = millis();
// Replace these values with data from your sensors/application. Variant data; data.set("deviceType", Variant("Particle device")); data.set("temperature", Variant(23.5)); data.set("humidity", Variant(60)); data.set("battery", Variant(87));
Particle.publish("tb/telemetry", data); }}Device OS 6.2 and later sends Variant data as CBOR, which is smaller over the air. Particle Device Cloud converts it back to JSON before it calls the webhook.
#include "Particle.h"
SYSTEM_THREAD(ENABLED);
unsigned long lastPublishMs = 0;const std::chrono::milliseconds publishPeriod = 30s;
void setup() {}
void loop() { if (Particle.connected() && millis() - lastPublishMs >= publishPeriod.count()) {
lastPublishMs = millis();
// Replace these values with data from your sensors/application. char payload[160]; snprintf(payload, sizeof(payload), "{\"deviceType\":\"Particle device\",\"temperature\":23.5,\"humidity\":60,\"battery\":87}");
Particle.publish("tb/telemetry", payload); }}The webhook sends ThingsBoard a body like this. The event data arrives as a JSON string in data:
{ "event": "tb/telemetry", "data": "{\"deviceType\":\"Particle device\",\"temperature\":23.5,\"humidity\":60,\"battery\":87}", "coreid": "3c003c000f51373331333230", "published_at": "2026-08-10T12:04:33.940Z"}Set Up Particle Integration
Section titled “Set Up Particle Integration”Create the uplink converter and the integration, then point a Particle webhook at the integration endpoint.
Create the Uplink Data Converter
Section titled “Create the Uplink Data Converter”The uplink converter parses the webhook body, then parses the event data inside it. Particle 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.
The decoder in this guide maps:
coreid⇾ device name.deviceTypefrom the event data ⇾ device profile. Default:Particle.published_at⇾ telemetry timestamp.- Other event data fields ⇾ telemetry.
coreidand the integration name ⇾ attributesparticleDeviceIdandintegrationName.
-
Go to Integrations ⇾ Data converters and click + Add data converter ⇾ Create new converter.
-
Converter type — keep Uplink.
-
Integration type — select Particle.
-
Name — enter a name, for example
Particle Uplink Converter. -
Main decoding configuration — the editor opens with the generic decoder template, which does not read the Particle webhook format. Replace it with the decoder below. The editor opens in TBEL; use the TBEL / JS toggle to switch to JavaScript.
/** Decoder **/// Particle webhook bodyvar data = decodeToJson(payload);// Particle event data must contain valid JSON.var eventData = decodeToJson(data.data);var deviceName = data.coreid;var deviceType = eventData.deviceType != null ? eventData.deviceType : "Particle";var groupName = "Particle devices";var timestamp = Date.now();if (data.published_at != null) {timestamp = new Date(data.published_at).getTime();}var attributes = {integrationName: metadata["integrationName"],particleDeviceId: data.coreid};// Move known non-telemetry fields into attributes when present.if (eventData.firmwareVersion != null) {attributes.firmwareVersion = eventData.firmwareVersion;}if (eventData.hardwareModel != null) {attributes.hardwareModel = eventData.hardwareModel;}// Exclude fields already used for device metadata/attributes.var excludeFromTelemetryList = ["deviceType","deviceName","firmwareVersion","hardwareModel"];var telemetry = toFlatMap(eventData, excludeFromTelemetryList, true);return {deviceName: deviceName,deviceType: deviceType,groupName: groupName,attributes: attributes,telemetry: {ts: timestamp,values: telemetry}};/** Decoder **/function decodeToString(bytes) {return String.fromCharCode.apply(String, bytes);}function flatten(obj, result, excludeList, prefix) {prefix = prefix || "";for (var key in obj) {if (excludeList.indexOf(key) !== -1) continue;var value = obj[key];var fullKey = prefix ? prefix + "." + key : key;if (typeof value === "object" && value !== null && !Array.isArray(value)) {flatten(value, result, excludeList, fullKey);} else {result[fullKey] = value;}}}var data = JSON.parse(decodeToString(payload));var eventData = JSON.parse(data.data);var deviceName = data.coreid;var deviceType = eventData.deviceType != null ? eventData.deviceType : "Particle";var groupName = "Particle devices";var timestamp = Date.now();if (data.published_at != null) {timestamp = new Date(data.published_at).getTime();}var attributes = {integrationName: metadata["integrationName"],particleDeviceId: data.coreid};if (eventData.firmwareVersion != null) {attributes.firmwareVersion = eventData.firmwareVersion;}if (eventData.hardwareModel != null) {attributes.hardwareModel = eventData.hardwareModel;}var excludeFromTelemetryList = ["deviceType","deviceName","firmwareVersion","hardwareModel"];var telemetry = {};flatten(eventData, telemetry, excludeFromTelemetryList);return {deviceName: deviceName,deviceType: deviceType,groupName: groupName,attributes: attributes,telemetry: {ts: timestamp,values: telemetry}};To adapt:
- Other telemetry — change the fields the firmware publishes. Every field not in
excludeFromTelemetryListbecomes telemetry; nested objects become dot-separated keys. - Device type — publish
deviceTypefrom the firmware, for examplePhoton 2orTracker One, or change theParticledefault. - Attributes — add fields such as a serial number to
attributesand toexcludeFromTelemetryList. - Plain-text event data — skip the second parse and store the value directly, for example
telemetry: { rawData: data.data }. - Several event types — branch on
data.event, for exampletb/telemetryandtb/status.
- Other telemetry — change the fields the firmware publishes. Every field not in
-
Click Test decoder function and check the result (see Test the Decoder).
-
Click Add.
Decoder Inputs
| Variable | Type | Content |
|---|---|---|
payload |
byte array | The webhook body: event, data, coreid, and published_at. |
metadata |
{[key: string]: string} |
integrationName, every request header as Header:<name>, and the key–value pairs from the integration’s Metadata field. |
Test the Decoder
Click Test decoder function below the editor. Set Payload content type to JSON, replace the payload with the sample webhook body from Publish Events from Firmware, and click Test. With the default Metadata (integrationName = Test PARTICLE), the Output panel shows:
{ "deviceName": "3c003c000f51373331333230", "deviceType": "Particle device", "groupName": "Particle devices", "attributes": { "integrationName": "Test PARTICLE", "particleDeviceId": "3c003c000f51373331333230" }, "telemetry": { "ts": 1786363473940, "values": { "temperature": 23.5, "humidity": 60, "battery": 87 } }}Check that deviceName holds the Device ID and telemetry.values holds the expected keys. 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 Particle.
- Name — keep the default
Particle integrationor enter your own. - Enable integration and Allow create devices or assets — keep both on, so the device is created on its first event.
- Click Next.
- Uplink data converter:
- Click Select existing and choose
Particle 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 call device functions.
- Connection:
- Copy the HTTP endpoint URL. You need it for the Particle webhook.
- Leave the other parameters at their defaults. See Connection Settings for details.
- Click Add.
Connection Settings
Section titled “Connection Settings”| Parameter | Description |
|---|---|
| Base URL | Address of your ThingsBoard server, used to build the endpoint URL. Example: https://thingsboard.cloud. |
| HTTP endpoint URL | Read-only. Format: {Base URL}/api/v1/integrations/particle/{integrationKey}, where integrationKey is a UUID assigned at creation. Accepts JSON bodies only. |
| Allow downlink | Lets the integration call Particle device functions. Off by default. When on, the Token field appears. See Configure Downlink. |
| Token | Particle access token that ThingsBoard sends as Authorization: Bearer <token> to the Particle Cloud API. Required when Allow downlink is on. |
| Enable security | Accepts only requests that carry the configured headers. See Secure the Endpoint with Header Filters. |
| Execute remotely | Runs the integration as a separate process outside the ThingsBoard cluster. ThingsBoard generates an Integration key and Integration secret for it. Particle needs a public endpoint, so the standard setup keeps this off. See Remote Integration. |
| Metadata | In Advanced settings. Key–value pairs added to the metadata of every message from this integration. |
Create the Particle Webhook
Section titled “Create the Particle Webhook”The webhook forwards matching events to the integration endpoint. Event name matching is a case-sensitive prefix: tb/ matches tb/telemetry, tb/status, and any other event that starts with tb/.
- Open Particle Console and select the sandbox, organization, or product that contains your device.
- In the left sidebar, open Cloud services and click Go to Integrations.
- Select Custom Webhook and click Start now.
- On the Webhook Builder tab, set:
- Name — for example
ThingsBoard. - Event name —
tb/. - URL — the HTTP endpoint URL of the integration.
- Request type —
POST. - Request format —
JSON. - Keep Status enabled.
- Name — for example
- Click Enable integration.
Test Uplink
Section titled “Test Uplink”Publish an event, then confirm that ThingsBoard decoded it and created the device.
Send a Test Uplink
Section titled “Send a Test Uplink”Flash the firmware from Publish Events from Firmware and wait for the device to connect. It publishes tb/telemetry every 30 seconds. The device’s Events tab in Particle Console shows each published event.
To test without a running device, open the webhook’s View integration page in Particle Console and click Test. Enter your Device ID, put a JSON object in Event data, and click Run test:
{ "deviceType": "Particle device", "temperature": 23.5, "humidity": 60, "battery": 87 }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
Particle Uplink Converterin Integrations ⇾ Data converters and click the Events tab. In shows the webhook body, Out shows the decoded result, and Metadata shows the request headers and integration name. - Device — go to Devices & assets ⇾ Devices. The integration has created a device named after the Device ID, in the
Particle devicesgroup. Open it and check the values on the Latest telemetry tab andparticleDeviceIdon the Attributes tab.
Secure the Endpoint with Header Filters
Section titled “Secure the Endpoint with Header Filters”Anyone who knows the endpoint URL can send data to it. A header filter rejects every request that does not carry the required headers with the exact values, and returns 403 Forbidden before the converter runs. Particle logs the rejected call as a hook-error event.
- In Particle Console, edit the webhook and add a custom HTTP header, for example
X-API-Keywith a secret value. - In ThingsBoard, open the integration and click Toggle edit mode (pencil icon).
- Turn on Enable security.
- Under Headers filter, click Add and enter the same Header and Value. A request must carry every header in the list. The value match is exact and case-sensitive.
- Click Apply changes.
Configure Downlink
Section titled “Configure Downlink”ThingsBoard sends each downlink right away as a Particle Cloud API call. Nothing is queued: if the device is offline or the call fails, ThingsBoard logs an error and drops the command.
The downlink converter turns an RPC request from the Rule Engine into a function call. For the full encoder reference, see Downlink data converter.
Create a Particle Access Token
Section titled “Create a Particle Access Token”ThingsBoard authenticates to the Particle Cloud API with an access token. The Call a function endpoint requires the devices.function:call scope.
For production, use a Particle API user limited to that scope. For a quick test, create a user token with the Particle CLI:
-
Install the CLI, then close and reopen the terminal:
Terminal window bash <( curl -sL https://particle.io/install-cli ) -
Log in to your Particle account:
Terminal window particle login -
Create a token that does not expire, and copy it. The CLI shows it only once.
Terminal window particle token create --never-expires
Register a Cloud Function in Firmware
Section titled “Register a Cloud Function in Firmware”The firmware below registers the function setOutput, which turns the D7 output on or off. Replace the pin and handler with your own control logic.
#include "Particle.h"
SYSTEM_THREAD(ENABLED);
int setOutput(String command);
void setup() { pinMode(D7, OUTPUT); digitalWrite(D7, LOW);
Particle.function("setOutput", setOutput);}
void loop() {}
int setOutput(String command) { if (command == "on") { digitalWrite(D7, HIGH); return 1; }
if (command == "off") { digitalWrite(D7, LOW); return 0; }
return -1;}The function name must match the RPC method. A cloud function takes one string argument and should return quickly: start long operations and return a status code.
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 access token.
- 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 Particle integration type are preselected.
- Enter a name, for example
Particle Downlink Converter. - Main encoding configuration opens with a generic encoder that does not set the Device ID. Replace it with the encoder below. The same code runs in TBEL and JavaScript.
- To record downlink events, click the debug icon next to the name, select All messages, and click Apply.
- Click Add.
- Enable downlink: turn on Allow downlink and paste the token from Create a Particle Access Token into Token.
- Click Apply changes.
Encoder used in this guide:
/** Encoder **/
// The integration calls the Particle function named by "method"// and passes "params" as its string argument.// msg.params is serialized JSON, for example "\"on\"": JSON.parse returns on.var result = { contentType: "JSON", data: JSON.stringify({ method: msg.method, params: JSON.parse(msg.params) }), metadata: { // Particle Device ID of the target device. // This guide uses it as the ThingsBoard device name. deviceId: metadata.deviceName }};
return result;The integration reads these fields from the encoder result:
| Field | Required | Description |
|---|---|---|
metadata.deviceId |
Yes | Particle Device ID used in the API URL. Without it, the downlink fails with Device id not found in metadata. |
method or function in data |
Yes | Name of the cloud function to call. |
params, command, or arg in data |
No | Function argument, sent as {"arg": "<value>"}. Numbers and booleans are converted to strings. A JSON object becomes an empty argument: to send it, pass msg.params as is, without JSON.parse. |
Configure the Root Rule Chain
Section titled “Configure the Root Rule Chain”Route RPC requests to the integration. Other message types have no method field, so the converter cannot turn them into function calls.
- Go to Data processing ⇾ Rule chains and open the Root Rule Chain.
- From the Action section, drag an integration downlink node onto the canvas.
- Enter a Name (for example,
To Particle integration), select your Particle integration, and click Add. - Connect the message type switch node to the integration downlink node with the RPC Request to Device relation.
- Click Apply changes.
Test Downlink
Section titled “Test Downlink”Add a dashboard switch that calls setOutput with on or off.
- Open a dashboard, click Edit mode, then click + Add widget.
- In the Control widgets bundle, select Switch Control.
- On the Data tab, set Target device to Device and select the Particle device.
- On the Appearance tab:
- Retrieve value using method — Don’t retrieve.
- RPC set value method —
setOutput. - Convert value function —
return value ? "on" : "off";
- Click Add, save the dashboard, and exit edit mode.
- Toggle the switch.
Each toggle produces this call:
RPC method: setOutput, params: "on" │ ▼POST https://api.particle.io/v1/devices/{deviceId}/setOutputAuthorization: Bearer <token>{"arg": "on"} │ ▼Particle.function("setOutput", ...) on the deviceVerify the Downlink
Section titled “Verify the Downlink”- Device — the
D7output (on many boards, the blue LED) turns on or off. - Converter events — open the downlink converter’s Events tab. In shows the RPC request, and Out shows the encoded
dataandmetadata.deviceId. - Integration events — open the integration’s Events tab. A failed call appears with status ERROR and the Particle API response.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
| Webhook does not fire | The event name does not start with the webhook’s Event name. | Publish tb/telemetry with a tb/ webhook. Matching is case-sensitive. |
| Firmware publish fails | The event name starts with the reserved particle or spark. |
Use a custom prefix such as tb/. |
Particle shows hook-error |
Wrong endpoint URL, unreachable ThingsBoard, or a failed header filter (403). |
Copy the HTTP endpoint URL again. Check that ThingsBoard is reachable over HTTPS and that the webhook sends the filtered headers. |
Device named api appears |
The event came from the CLI or Cloud API, not from a device. | Test from the device or from the webhook’s Test dialog with a Device ID. |
| Device not created | Allow create devices or assets is off, or coreid is missing. |
Turn the option on. Keep the default webhook fields. |
Converter error on data |
The event data is plain text or invalid JSON. | Publish valid JSON, or map data.data directly (see To adapt). |
| Telemetry saved as one string | The decoder stores data without parsing it. |
Parse data.data as JSON and flatten the fields. |
Downlink 401 or 403 |
The token is missing, expired, revoked, or lacks devices.function:call. |
Create a new token and paste it into Token. |
Downlink 404 |
The device is offline, the function is not registered, or the device name is not the Device ID. | Check that the device is online, that the RPC method matches the Particle.function() name, and that metadata.deviceId holds the Device ID. |
| No downlink and no error | Allow downlink is off, no downlink converter is assigned, or the rule chain does not route RPC requests. | Check the integration settings and the RPC Request to Device relation. |
Device id not found in metadata |
The encoder does not set metadata.deviceId. |
Use the encoder from this guide. |
| Duplicate or out-of-order telemetry | Particle delivers webhooks on a best-effort basis. | Order data by published_at, and deduplicate where it matters. |
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 — decoder inputs, output format, and scripting patterns.
- Downlink data converter — encoder reference.
- Integration downlink node — route Rule Engine messages to an integration.
- TBEL — built-in functions and syntax for converter scripts.
- Particle webhooks — event filters, default fields, templates, and delivery.
- Particle.publish() and typed publish — publish events from Device OS.
- Particle.function() — expose a device function through Particle Device Cloud.
- Particle Cloud API — the Call a function endpoint and its scope.
- Particle access tokens — user tokens, API users, and token security.
Was this helpful?