Skip to main content

6.3 Sending Data

In example 6.1 the board asked a website for its contents and printed the answer. That traffic went one way: the internet talked, the board listened. This example turns it around. The NULA MINI makes up a number every five seconds and sends it out to a server on the internet, and you watch each one arrive in a browser tab.

That is the half of the conversation every connected project needs. A thermometer that logs to a spreadsheet, a doorbell that pushes a notification, a plant sensor that emails you when the soil dries out: all of them are doing what this example does, with a real measurement in place of the random number.

In this documentation you will learn:

  • The difference between an HTTP GET and an HTTP POST request.
  • How to send data out from the board with the HTTPClient library.
  • How to catch and inspect that data using webhook.site.
  • Why the server sees a different IP address from the one your board prints.

Hardware required

  • 1× Soldered NULA MINI board
  • 1× USB-C cable
  • 1× breadboard
  • A 2.4 GHz Wi-Fi network with internet access
ℹ️
No components, no resistors, no jumper wires. Exactly as in example 6.1, nothing connects to the board's pins. The USB-C cable is the only wire involved, and all of the work happens in software.
ℹ️
This example needs a real internet connection. Example 6.2 only needed your board and your phone on the same network, and worked perfectly well on a router with no internet at all. This one does not: webhook.site is a public server out on the internet, so the network has to be able to reach it. A guest network that blocks outbound connections will fail here.
ℹ️
The network must be 2.4 GHz. The ESP32-C6 chip has no 5 GHz radio, so a 5 GHz-only network is invisible to it. If your router splits the two bands into separate names, pick the 2.4 GHz one.

Putting the components together

1. Insert the NULA MINI board on the breadboard

Push the board into one end of the breadboard so that its two rows of pins straddle the centre channel.

NULA MINI board seated on the breadboard
Step 1: the board seated on the breadboard

2. Connect the USB-C cable

Plug the USB-C cable into the connector on the edge of the board, and the other end into your computer. The PWR LED lights up, and the build is finished.

NULA MINI board on the breadboard with only the USB-C cable attached and the PWR LED lit
Step 2: the finished build. Not one hole of the breadboard is in use
ℹ️
The breadboard is only holding the board steady. Not a single hole is used, and the example works just as well with the board sitting loose on the desk.

Setting up webhook.site

Before uploading anything, you need somewhere for the data to go. webhook.site is a free service that hands you a web address of your own and then shows you, live, everything that gets sent to it. It saves you from having to write a server before you can test sending to one.

Open webhook.site in a browser. It gives you a fresh address the moment the page loads. There is nothing to sign up for.

The webhook.site landing page showing a freshly generated unique URL and an empty inbox
A freshly opened webhook.site page. The address under Your unique URL is the one to copy
  1. Copy the address shown under Your unique URL. It looks like https://webhook.site/0306ed74-f1dd-4ab4-ba2c-74a20c7658e4. The long code at the end is yours alone, and yours will be different from the one in the picture.
  2. Paste it into the sketch, between the quotation marks of the webhookURL variable:
const char* webhookURL = "https://webhook.site/0306ed74-f1dd-4ab4-ba2c-74a20c7658e4";
  1. Leave the browser tab open. This is the page the data will appear on, and it updates by itself, so you never need to reload it.
ℹ️
Keep an eye on the request counter. Just under the toolbar the page reads INBOX (0/50) while it is waiting. A free webhook.site address holds 50 requests, and this sketch sends one every five seconds, so it fills up in a little over four minutes, after which new requests stop being recorded. If yours goes quiet, that is almost always why. Press RST on the board to stop and start the flow, or click New at the top of the page for a fresh address with an empty inbox. The 7d badge beside the address means it is kept for seven days.
ℹ️
Anyone who knows your unique address can send data to it, and anyone who has it can read what was sent. It is fine for learning with random numbers, but do not send anything private to it.

GET and POST

HTTP, the language browsers and servers speak, has a handful of methods, the word at the very start of a request that says what kind of thing is being asked for. Two of them cover almost everything:

  • GET means give me something. Your browser sends a GET every time you open a page. Example 6.1 sent a GET to example.com and got a page of HTML back.
  • POST means here is something, take it. It is what your browser sends when you fill in a form and press submit, and it is what this example uses.

The practical difference is where the data rides. A GET request is essentially just an address, so there is nowhere to put much of anything. A POST request carries a body, a separate parcel of data attached underneath the address, and that is where our number goes.

The sketch builds that body as a single line of text:

String postData = "number=" + String(randomNumber);

which comes out as number=77, or number=80, or whatever the board just made up. The name=value shape is the same one a browser uses for a simple form, and it has a name: form encoding. The server has no way of guessing which format you chose, so the sketch tells it, in a header:

http.addHeader("Content-Type", "application/x-www-form-urlencoded");

A header is one line of extra information about the request itself, rather than part of the data. This particular one is what lets webhook.site show your number as a tidy labelled field instead of a raw blob of text, as you will see further down.


Code

Below is the complete code for this example:

/**
**************************************************
*
* @file 6.3_Sending_Data.ino
* @brief Example that shows how to send data from the NULA board to a server on the internet using an HTTP POST
* request. Every few seconds the board makes up a random number and sends it to a webhook, which is a
* web address that simply collects whatever is sent to it and shows it to you in your browser.
* In example 6.1 we read data from the internet, here we write data to it.
* For details, connection diagram and more, check out the example documentation at: <link placeholder>
* @author Soldered
***************************************************
*/

/*
The WiFi library contains everything needed to join a Wi-Fi network.
*/
#include <WiFi.h>

/*
The HTTPClient library lets us speak HTTP, the language browsers use to talk to websites.
*/
#include <HTTPClient.h>

/*
These two variables hold the name of your Wi-Fi network (the SSID) and its password. Replace the text between the
quotation marks with your own network details.
*/
const char* ssid = "your ssid";
const char* password = "your password";

/*
This variable holds the address we send our data to. Open https://webhook.site in a browser, copy the unique link it
shows you at the top of the page, and paste it between the quotation marks below. Keep that browser tab open and you
will see every value the board sends appear in it.
Example: https://webhook.site/your-unique-id
*/
const char* webhookURL = "your unique url";

/*
This variable defines how much time passes between two messages, in milliseconds. 5000 milliseconds is five seconds.
Feel free to experiment with this value, but be aware that sending data very often is impolite towards whichever
server is receiving it.
*/
const unsigned long POST_INTERVAL = 5000;

/*
This variable remembers the moment when we sent the last message, so we know when the next one is due.
*/
unsigned long lastPost = 0;

void setup() {

/*
Serial.begin() establishes serial communication between your board and your computer via a USB cable. We use it here
to follow the connection process and to see what the server answers.
*/
Serial.begin(115200);
Serial.println();
Serial.println("Wi-Fi POST Request Example");

/*
randomSeed() gives the random number generator a starting point. Without it the board would produce the very same
sequence of "random" numbers after every reset, which is easy to mistake for a broken program. esp_random() reads the
hardware random number generator built into the chip, which gives us a genuinely different starting point every time.
*/
randomSeed(esp_random());

/*
WiFi.begin() starts the connection attempt using the network name and password we defined above. The function only
starts the process, it does not wait for it to finish.
*/
WiFi.begin(ssid, password);
Serial.print("Connecting to Wi-Fi");

/*
Because of that, we wait for the connection ourselves. WiFi.status() tells us the current state and WL_CONNECTED is
the value it reports once we are online. One dot is printed every half second so we can see the board is still
trying.
*/
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}

/*
WiFi.localIP() returns the address the router handed out to our board.
*/
Serial.println();
Serial.println("Connected to Wi-Fi!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
}

void loop() {

/*
millis() is a function that returns the number of milliseconds passed since the board began running the current
program. We use it instead of delay() so the board stays free to do other work between messages.
*/
unsigned long now = millis();

/*
Here we check how much time has passed since the last message. Only when POST_INTERVAL milliseconds have gone by do
we send a new one, and we immediately remember the current time as the new starting point.
*/
if (now - lastPost >= POST_INTERVAL) {
lastPost = now;

/*
Before sending anything we check that we are still online. A Wi-Fi connection can drop at any time, and trying to
send data without one would only waste time and print errors.
*/
if (WiFi.status() == WL_CONNECTED) {

/*
Here we create our HTTP client object, which we named "http". We create it inside the if statement so that a
fresh one is used for every message.
*/
HTTPClient http;

/*
random() is a function that returns a whole random number. The first value is the lowest number it may return
and the second one is the first number it may not return, so this call gives us a number from 0 to 100.
In a real project this is where a sensor reading would go.
*/
int randomNumber = random(0, 101);

/*
Here we build the data we are going to send. The format "name=value" is the same one a browser uses when you
submit a simple web form, and String() converts our number into text so it can be joined to the rest with a plus
sign.
*/
String postData = "number=" + String(randomNumber);

Serial.println("----------------------------------");
Serial.println("Sending POST request to webhook.site...");
Serial.print("Data: ");
Serial.println(postData);

/*
begin() prepares the request by telling the client which address we want to reach. Nothing is sent yet.
*/
http.begin(webhookURL);

/*
addHeader() adds one line of extra information to the request. A header tells the server something about the
request itself, and this particular one tells it in which format our data is written, so it knows how to read it.
*/
http.addHeader("Content-Type", "application/x-www-form-urlencoded");

/*
POST() sends the request together with our data and waits for the answer. POST is the HTTP method used for
sending data to a server, while GET, which we used in example 6.1, is the one used for reading data from it.
The function returns a response code: numbers of 200 and above mean the server answered, while a negative number
means we never got an answer at all.
*/
int httpResponseCode = http.POST(postData);

/*
Here we check whether the server answered. If it did, we print the response code and the body of the answer.
*/
if (httpResponseCode > 0) {
Serial.print("Server response code: ");
Serial.println(httpResponseCode);
String response = http.getString();
Serial.println("Response body:");
Serial.println(response);
} else {

/*
errorToString() turns the negative error code into a short sentence, which is much easier to understand than
the number on its own.
*/
Serial.print("POST failed. Error: ");
Serial.println(http.errorToString(httpResponseCode));
}

/*
end() closes the connection and frees the memory the client was using. Always close a connection once you are
done with it, otherwise a program that makes many requests will slowly run out of memory.
*/
http.end();
} else {

/*
If we are not connected, we simply start a new connection attempt and try again on the next pass.
*/
Serial.println("Wi-Fi not connected. Trying to reconnect...");
WiFi.begin(ssid, password);
}
}
}
ℹ️
About that randomSeed() line. On a classic Arduino, random() produces the very same sequence of numbers after every reset unless you seed it first, and the comment in the sketch describes that behaviour. The ESP32-C6 does not work that way: random() on this chip already draws from a hardware random number generator, so the numbers differ every run whether you seed it or not. The line does no harm, since it is seeded from that same hardware source and the numbers stay unpredictable, but you can delete it on this board without changing anything you can observe.

What you should see

Upload the sketch, then open the Serial Monitor and set it to 115200 baud. Unlike example 6.1, where everything happened once in setup(), this sketch keeps going. A new block of output appears every five seconds for as long as the board is powered, so there is no burst to miss.

Serial Monitor showing the Wi-Fi connection sequence followed by a POST request and the server's response
The Serial Monitor after the first POST request. The board and port are named along the top; the baud selector sits at the far right of that bar, just outside this crop

Written out, the first cycle reads:

ESP-ROM:esp32c6-20220919
Build:Sep 19 2022
rst:0x1 (POWERON),boot:0x6e (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:2
load:0x40875730,len:0x1278
load:0x4086b910,len:0xc58
load:0x4086e610,len:0x31c0
entry 0x4086b910

Wi-Fi POST Request Example
Connecting to Wi-Fi............
Connected to Wi-Fi!
IP Address: 192.168.75.75
----------------------------------
Sending POST request to webhook.site...
Data: number=77
Server response code: 200
Response body:
This URL has no default content configured. <a href="https://webhook.site/#!/edit/0306ed74-f1dd-4ab4-ba2c-74a20c7658e4">Change response in Webhook.site</a>.

Reading it from the top:

The first nine lines are not your sketch. ESP-ROM, SPIWP and the three load: lines are the chip's built-in bootloader reporting for duty before your program starts. They print on every reset, in every example. Your sketch begins at the blank line, which is there because setup() calls Serial.println() with nothing in it, purely to separate the two.

Count the dots to see how long the connection took. There are twelve here, one every half second, so this board was on the network in about six seconds. A row that keeps growing past twenty or thirty means the credentials are wrong or the network is 5 GHz.

Server response code: 200 is the line that matters. It means webhook.site received the request and accepted it. Any code from 200 upwards is the server answering; a negative number means the board never got an answer at all.

Response body: is followed by a sentence that looks like a complaint, and is not one. This URL has no default content configured. is simply webhook.site telling you that you never told it what to reply with, so it replied with that. Your data still arrived. A POST is about what you send, not about what comes back, and most real servers answer with something equally uninteresting, often nothing at all.

The data arriving in the browser

Now switch to the webhook.site tab. Requests appear on their own as the board sends them.

webhook.site listing a column of received POST requests, with one opened to show the number form value
Twelve POST requests received, with one of them opened. The client IP address has been blanked out in this picture

The column on the left is every request that has arrived, newest at the top. Click any one of them to open it on the right. There is a lot on this screen, and five parts of it are worth finding:

The timestamps are exactly five seconds apart: 10:26:43, 10:26:38, 10:26:33, and so on down the column. That is POST_INTERVAL doing its job, and it is far better proof of the timing than the Serial Monitor can give you. Change the value in the sketch and the spacing of this column changes with it.

Form values: number 80 is your data, taken apart and labelled. This is the payoff from the Content-Type header: because the sketch declared the format, webhook.site knew to read number=80 as a field called number holding the value 80. Remove that header and the same bytes still arrive, but this section disappears and you are left picking the value out of the raw text yourself.

Raw Content: number=80 is that same body exactly as it left the board, before anything interpreted it. Note that it says 80 while the Serial Monitor above said 77. These are two different requests, five seconds apart, each carrying a freshly made-up number.

9 bytes, on every row. That is the whole message: seven characters of number= plus two digits. A single-digit number would show as 8 bytes. It is worth pausing on how little data this is next to the kilobyte of HTML that example 6.1 pulled down.

user-agent: ESP32HTTPClient is your board introducing itself. Every HTTP request says what made it. Your browser puts its own name and version here, and the HTTPClient library fills this in for you.

ℹ️
The Host address is not the address your board printed. The Serial Monitor said IP Address: 192.168.75.75, but the request arrived from something entirely different (blanked out in the picture above). Neither is wrong. 192.168.75.75 is the board's address inside your own network, handed out by your router and meaningless anywhere else. Every example so far has printed one of these. When the request leaves for the internet, your router swaps that private address for the single public one your whole household shares. So webhook.site sees your router, not your board, which is also why the Location line names a city near you rather than your desk.

If it does not work

  • Dots that never stop. The credentials are wrong, or the network is 5 GHz, or the router is out of range. Check the SSID and password for capital letters and stray spaces.
  • Server response code: 200 in the monitor, but nothing in the browser. Either the address in webhookURL is not the one the open tab is showing (check the long code at the end character for character), or the inbox has hit its 50-request limit. Click New for a fresh address.
  • Requests arrive for a few minutes and then stop. That is the 50-request limit. The board carries on sending and still gets its 200; webhook.site has simply stopped recording.
  • A negative response code. The board is on the network but cannot reach the internet. Usually the network has no working connection, or a guest network is blocking outbound requests. The sentence printed after POST failed. Error: narrows it down.
  • Unreadable symbols instead of words. The Serial Monitor is at the wrong baud rate. It must be 115200.
  • No serial output at all. The board is not on the port shown at the top of the window. On Windows, a board that never appears as a port at all needs the CH340 driver.

Full example

Check out the full example code on the link below:

6.3_Sending_Data.ino

Example that shows how to send data from the NULA MINI board to a server on the internet using an HTTP POST request, and how to watch it arrive on webhook.site.