This is an interactive tutorial that shows how to use Particle.publish to send data from a Particle device to a cloud service by a webhook. You'd use a technique like this for sending sensor or location data from your Particle device to the Particle cloud and then to your servers via a webhook.
If you're interesting in controlling devices and the differences between publish and functions, see the function and publish demo.
In order to use this tutorial, you must be logged into your Particle account:
Logged into Particle as
Organization:
Log in to your Particle account
Log in manually (not using single sign-on)
Log in using:
Username (Email):
Password:
Maximum time to stay logged in:
Access token:
MFA OTP Token:
Login failed. Try again.
Cloud setup
In this section we'll set up a product, add your device, and create a webhook.
Select a product
You should typically start with a product. You'll eventually need one to scale, and it makes it easier to group devices.
This demo requires a product, and each product can only have a single device platform, so you must select that first.
Device platform:
The product cannot be changed once the demo has been started.
You can find all of your sandbox products in the productsTab in the Particle console.
There is no charge for creating products in your free developer sandbox and there is no limit on the number of products, though there is a limit to the number of devices in the free plan.
Start demo
Starting the demo will start the webhook server and start monitoring events.
Start server
Add devices to product
Whenever you have a product, you must add devices to it. When you scale, you will typically add all of the devices in your order at once, as you will be emailed a list of the Device IDs in your order. In the growth and enterprise plans, just adding the Device ID to the product does not affect billing; billing only starts the first time the device connects to the Particle cloud.
For this demo, you can just select existing devices from your developer sandbox to use in this tutorial. If you do not have a device yet, you can skip this step and use the Test feature to show how the webhook works without a device.
You will normally use the Add Devices button in the devices tab in the console to do this in your real products.
Create a product webhook
Normally you'll create a webhook in your product from the console. Because the server URL changes every time you start the demo, the tutorial will just create it for you automatically.
A product has not been selected yet. Select a product above first.
The server has not yet started so the webhook cannot be created because the URL is not yet known.
The webhook has already been added to the product
Start Demo to start up the webhook service, as the server URL changes each time you run the demo.
Tell me more about creating a webhook
Each webhook contains a number of parameters. In this webhook:
The event name. When an event is published from the device or API, if the event name begins with this string, the webhook will be triggered.
Note that this is a prefix match, not a string match! This is why the event name is set to WebhookDemo01. If it were just WebhookDemo it would trigger on
WebhookDemo as well as WebhookDemo02, which is probably not what you want to do. However, you can take advantage of the prefix to use a single
webhook to handle multiple things, which is easier to maintain than a large number of webhooks.
integration_type: This is webhook. There are special integrations for Azure, Google Cloud, etc. but this is the setting to connect to your own server.
headers: This specifies any additional headers to pass to your server. This demo includes two:
Content-Type: The kind of data we're sending from the webhook.
Authorization: This is optional, but provides a good way to authenticate your webhook, described in greater detail below.
url: The server URL. This will change every time you run the demo or refresh this page. This is specific to how this demo runs in your browser; your server URL will probably not change.
Note that this URL is https (TLS/SSL encrypted). This is not required, but highly recommended for security reasons.
noDefaults: This indicates that default fields should not be added to the POST data, as we manage them ourselves.
method: The demo server expects POST. Other common methods are GET, PUT, and DELETE.
body: This is the data that will be sent to your server. This will be explained in more detail below.
responseTopic: When the response from the webhook server is returned, this is the event that is used. You device may want to subscribe to this to get the result on-device.
errorEesponseTopic: When the webhook server returns an error, this is the event that is used.
Body
The body declaration creates a new JSON-formatted object containing:
The requesting Device ID (it's api if testing the webhook).
A timestamp when the event was received by the Particle cloud (not the time it was generated on-device).
A key sensor which contains the JSON object that the device published. This allows the device to send additional fields without having to modify the webhook.
Data operations
Every time a device publishes an event, it incurs one data operation.
The webhook usage is not metered
If you subscribe to an responseTopic on your device, it will count as an additional data operation, thus counting as two total in most cases.
Make sure you configure the responseTopic correctly, however, as doing it incorrectly can cause an avalanche of data operations in large fleets!
Response topic
The response topic is /hook-response/. The reason it begins with is that it
allows the device to subscribe to responses only from webhooks it triggers. If you left this out, the device would receive the response from every device
in your fleet, which is probably not what you want, and will cause a huge number of data operations to be used.
Chained events
You cannot chain webhooks. In other words, if you create a webhook that has an event name corresponding to a response topic (hook-response), it will never be triggered.
This is done to prevent loops, which could easily get out of control causing excessive data operations and server load.
Security
To help secure your webhook:
Be sure you are using https (TLS/SSL encrypted).
Use a valid SSL certificate from a known provider (not a self-signed certificate).
Including a Authorization header in the webhook with a pre-shared random key can make your connection more secure if you also check it in your server.
Your device fleet
This control shows the status of devices in your product fleet. It's similar to the devices tab in your product in the console.
Once you've added a device to your product, it will show up here. Use the Flash link to flash the product firmware to your device.
The online column shows a green checkbox if the device is online and connected to the Particle cloud.
The firmware column shows a green checkbox if the device has product firmware and has come online at least once.
When you onboard your first device, you have to manually request the firmware be flashed to it. Click the Flash link to do this.
The development column checkbox shows if the device has the Mark as Development device flag set. You can also change the
state using this checkbox. You normally should leave this turned off.
Setting up product firmware involves several steps:
Compiling the firmware, typically using Particle Workbench.
Uploading to the console from the firmware tab of your product.
Flashing the firmware to at least one device manually.
Releasing the firmware as the default firmware for the product.
Once you've set the default firmware, and newly added device will automatically be flashed with this firmware when it connects to the cloud.
Test webhook
A product has not been selected yet. Select a product above first.
The server has not yet started so the webhook cannot be created because the URL is not yet known.
The webhook has not yet been created in the product.
Event data sent:
Start Demo to interact with it, then complete the steps to set up your webhook.
Tell me more about testing a webhook
This is similar, but not the same, as the Test button in the webhook configuration in the console.
This button publishes and event using the Particle cloud API, but it includes simulated data, similar to what the device firmware below sends.
When you use the API to create an event, the source Device ID is "api" instead of an actual Device ID. You cannot simulate sending from a Device ID
using the API. This is intentional to prevent being able to spoof data from a device.
Explainer
Once you start the demo and events start flowing, either by using the Test button above, or from a device, the section below will explain what is happening.
Start Demo to begin listening for events in your product.
Webhook server data
The table below is the decoded data that was received by the webhook server. This is the kind of data that you'd be able to use in a custom web or mobile app, but you'd probably use something fancier than just a table.
In this demo, the webhook sends POST data in JSON format. The server data shown is the decoded version of this data.
Additionally, fields that you sent up from the device are also broken out into their own columns.
This demo is just to show decoded data. In you server, you'd probably do something more useful than just displaying it in a table. You might store the data in a database, do calculations with the data, or alert on values out of range.
Event log
This control shows the same information that is shown in the events tab in your product so you don't need to switch between multiple windows.
Name
Data
Device
Published At
Start Demo to begin listening for events in your product.
Device firmware
Tell me more the device firmware
This is standard boilerplate you'll see in most applications.
Using SerialLogHandler is preferable to using Serial.print for debugging.
The code defines the name of the event to publish here. If the name of the webhook prefix matches this string, the webhook will fire. For example:
Event
Webhook is run?
WebhookDemo01
Yes
WebhookDemo
No
SomethingElse
No
WebhookDemo011
Yes
WebhookDemo01Test
Yes
const char *eventName = "WebhookDemo01";
This sets how often the firmware publishes, in this example, every 5 minutes, starting immediately after connecting to the cloud. Other examples would be "30s" for 30 seconds or "1h" for 1 hour. Beware of short intervals as they can use a large number of data operations!
Hint: lastPublishMillis could be initialized to 0 instead. This would wait 5 minutes before the first publish. This is a good safety net because if a bug causes your device to reboot continuously, it could result in an excessively large number of publishes.
const std::chrono::milliseconds publishInterval = 5min;
unsigned long lastPublishMillis = -publishInterval.count();
These global variables are described below, where they're used.
int hookSequence = 0;
bool buttonClicked = false;
These are forward declarations - function names that are used before they are implemented in the .cpp file.
If your main source file is .ino instead of .cpp, you don't need forward declarations because they're generated for you, but it's better programming practice to use .cpp files and include them explicitly.
This is the standard setup function. The first thing it does is register a handler to receive the hook-response. This must match what is configured in the webhook.
Note that the event name begins with the Device ID (24 character hexadecimal). This is done so the device only receives the hook response for the events
that this device publishes. If you don't do this in both the webhook and the C++ source code, then the device will receive a response for any device in the
product that publishes to the webhook, which is probably not what you want, and can generate a very large number of data operations.
This code also publishes when you click the MODE button. You probably won't do this in your production code, but it's handy during testing.
// Register a click handler for the MODE button
System.on(button_click, clickHandler);
When this code publishes an event, it includes an id number in it that increases with each publish. This is good practice, and it makes it easier
to de-dupe events received multiple times. It is possible for your webhook to be called more than once for the same event in the case that the
event ACK is lost. A sequence number makes it easier to detect this.
While it's usually fine to start with a sequence of 0, another option is to seed it with a random number. In order for it to actually be random
you need to wait until after connected to the Particle cloud. At boot, the rand() function is not only pseudo-random, but unseeded so every device
at boot has the same value of rand(). Once cloud connected it gets seeded from a random number from the cloud. At that point, it's still
pseudo-random (linear congruential generator, LCG, as implemented in glibc), but at least it will start from a different random point.
if (hookSequence == 0 && Particle.connected())
{
// Wait until Particle.connected because the rand() is seeded from the cloud
hookSequence = rand();
}
This is how time-based publishes work. Additionally:
Always check Particle.connected() before attempting to publish.
When doing time-based calculations, always follow the pattern here. Storing the next time to publish and other techniques can fail when the millis() counter rolls over to 0 every 49 days. The code as written here is safe cross rollover.
The button handler can run as an interrupt service routine, so you can't publish directly from the handler. Instead, set a flag and handle it from loop. If you are using a physical button connected to a GPIO, don't forget to add debouncing code.
if (buttonClicked)
{
buttonClicked = false;
if (Particle.connected()) {
publishSensorData();
}
}
This code doesn't really do anything with the webhook response; it just writes it to the USB serial debug console. But you could add code here to do something more useful.
Don't publish from a subscription handler! They share a single buffer and the data will be corrupted.
This function just simulates data so you don't need to wire up an actual temperature sensor to your device. You could replace this code with code that actually reads a temperature sensor.
float readTemperatureC()
{
// This code doesn't actually read the temperature, it just returns a random number,
// but you could easily put the code to read an actual sensor here.
static float lastTemperatureC = -100;
if (lastTemperatureC != -100) {
lastTemperatureC += (float) ((rand() % 6) - 3);
}
else {
lastTemperatureC = (float) (rand() % 40);
}
return lastTemperatureC;
}
This is the function that is called to publish data to the Particle cloud.
A buffer is allocated to hold the event, which can be up to 1024 bytes of data.
Formatting the data in the publish as JSON is recommended for flexibility and easy of maintenance.
And finally publish the data to the Particle cloud. You may see example code that includes PRIVATE, which is no longer necessary as all events are private.
Particle.publish(eventName, publishDataBuf);
Open in Workbench
The recommended development environment for Particle firmware is Particle Workbench. To open this project in Workbench:
Use the Download Full Project (*.zip) option in Device Firmware, above.
Extract the zip file on your computer.
Open Visual Studio Code
Use the File - Open option to open the directory you just extracted. Make sure you open the directory, not a single file in it.
Most operations are in the command palette (Ctrl-Shift-P on Windows and Linux, Command-Shift-P on Mac). Some useful commands:
Particle: Login Log into your Particle account.
Particle: Configure Project for Device Set the type of device and the version of Device OS you want to use.
Particle: Cloud flash Compile the project and flash it to a device OTA.
Particle: Cloud compile Compile the project and download the .bin file.
Device logs
If you have your Particle device connected by USB to your computer (Windows, Linux, Mac, or Chromebook), and you are using the Chrome web browser, you can monitor your device's USB serial debug log from this interactive control. You can also use particle serial monitor from the Particle CLI if you prefer. Both are optional, but are good for troubleshooting.
More things to try
Expand your device fleet
Use the Add Devices button in the devices tab in the console to add another device to your project.
You can monitor both devices from this page!
Upload product firmware
Use Workbench to build a firmware binary, then upload the binary to firmware tab in your product in the console.
You can use this to flash all devices in your product fleet with new firmware in just a few clicks.
Also, once you set default product firmware and add a device, it will be immediately flashed with your product firmware if online, or automatically when it next comes online.
Add more data fields
Try adding additional data fields to your product firmware using the JSONWriter. You can add integer, floating point, and string variables easily. These will automatically be included via
the webhook and will show up in the data table.
Add a real sensor
Instead of just using random data, try connecting a real temperature sensor and hooking it up into the readTemperatureC() function.
Clean up
If you are done using this tutorial, you can clean up the things that were created during this tutorial.
You must Start Demo to retrieve product information before it can be cleaned up.
You should flash Tinker using the option above or turn the device off after completing the tutorial so it doesn't continue to publish events,
which will consume data operations even if you're not actively using the tutorial.