An ESP32 on your desk is a gadget. An ESP32 that can talk to the internet is a connected device: it can fetch the weather, post a temperature reading to a dashboard, call a webhook when a door opens, or read a price from an API. All of that is built on the same two operations, HTTP GET (ask for data) and HTTP POST (send data).
The ESP32 Arduino core includes an HTTPClient library that handles both, including HTTPS. The code is short, but the details decide whether it works reliably: parsing JSON without running out of memory, using HTTPS correctly, handling errors instead of freezing, and not hammering a server with requests every few milliseconds. This guide walks through each of those with code you can paste and run.
GET vs POST: What Each One Does
| GET | POST | |
|---|---|---|
| Purpose | Ask a server for data | Send data to a server |
| Data location | In the URL (query string) | In the request body |
| Typical ESP32 use | Read weather, time, a status | Upload sensor readings, trigger an action |
| Size limits | Short URLs only | Much larger payloads |
| Safe to repeat? | Yes | Not always |
Never put passwords or private keys in a GET URL. They end up in server logs. Put them in a header or a POST body instead.
You will need a working WiFi connection first. If you have not set that up, start with the ESP32 WiFi tutorial.
Your First GET Request
This sketch connects to WiFi, requests a page and prints the response.
#include <WiFi.h>
#include <HTTPClient.h>
const char* ssid = "YOUR_WIFI_NAME";
const char* password = "YOUR_WIFI_PASSWORD";
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(500);
Serial.println("WiFi connected");
HTTPClient http;
http.begin("http://worldtimeapi.org/api/timezone/Etc/UTC");
int code = http.GET();
if (code > 0) {
Serial.printf("HTTP code: %d\n", code);
String payload = http.getString();
Serial.println(payload);
} else {
Serial.printf("Request failed: %s\n", http.errorToString(code).c_str());
}
http.end(); // always release the connection
}
void loop() {
}
Three things to notice:
http.GET()returns the HTTP status code (200 OK, 404 Not Found) or a negative number for an ESP32-side failure.http.getString()reads the full body into memory, which is fine for small responses.http.end()frees the connection. Forgetting it leaks memory over time.
Public APIs change or disappear, so if this exact URL stops responding, substitute any simple endpoint you trust. What matters is the pattern.
Understanding Response Codes
| Code | Meaning | What to do |
|---|---|---|
| 200 | OK | Success |
| 201 | Created | A POST created something |
| 301, 302 | Redirect | Enable redirect following (see below) |
| 400 | Bad request | Check your body or parameters |
| 401, 403 | Unauthorized, forbidden | Check your API key or token |
| 404 | Not found | Check the URL |
| 429 | Too many requests | Slow down |
| 500, 503 | Server error | Retry later |
And the negative codes, generated by the ESP32 itself:
| Code | Name | Usual cause |
|---|---|---|
| -1 | Connection refused | Wrong host or port, server down, no WiFi or no DNS |
| -2 | Send header failed | Connection dropped immediately |
| -3 | Send payload failed | Connection dropped while sending |
| -4 | Not connected | WiFi is down |
| -5 | Connection lost | Network dropped mid-request |
| -7 | No HTTP server | Wrong port or not an HTTP service |
| -8 | Too little RAM | Response too large, free memory |
| -11 | Read timeout | Server too slow or unreachable |
http.errorToString(code) turns any of these into readable text.
GET With JSON: Reading Weather Data
Most APIs return JSON. Install ArduinoJson (version 7) from the Library Manager. This example reads the current temperature from Open-Meteo, a free weather API that needs no key.
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
const char* ssid = "YOUR_WIFI_NAME";
const char* password = "YOUR_WIFI_PASSWORD";
const char* url =
"https://api.open-meteo.com/v1/forecast?latitude=51.5&longitude=-0.12¤t=temperature_2m,wind_speed_10m";
void fetchWeather() {
WiFiClientSecure client;
client.setInsecure(); // see the HTTPS section below
HTTPClient http;
http.begin(client, url);
http.setTimeout(8000);
int code = http.GET();
if (code == HTTP_CODE_OK) {
String payload = http.getString();
JsonDocument doc;
DeserializationError err = deserializeJson(doc, payload);
if (!err) {
float temp = doc["current"]["temperature_2m"];
float wind = doc["current"]["wind_speed_10m"];
Serial.printf("Temperature: %.1f C, Wind: %.1f km/h\n", temp, wind);
} else {
Serial.printf("JSON error: %s\n", err.c_str());
}
} else {
Serial.printf("HTTP error: %d (%s)\n", code, http.errorToString(code).c_str());
}
http.end();
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(500);
fetchWeather();
}
void loop() {
}
For large responses, filter while parsing so you keep only the fields you need:
JsonDocument filter;
filter["current"]["temperature_2m"] = true;
deserializeJson(doc, payload, DeserializationOption::Filter(filter));
Filtering keeps memory use small, which matters on a chip with about 300 KB of usable heap.
HTTPS: Secure Requests
Most modern APIs require HTTPS. In the example above, client.setInsecure() makes the connection encrypted but does not verify the server's identity. That is acceptable for learning and for public data, but it is vulnerable to impersonation, so avoid it when you send secrets or control something important.
To verify the server, give the client the server's root certificate:
const char* rootCA = \
"-----BEGIN CERTIFICATE-----\n" \
"...paste the root certificate here...\n" \
"-----END CERTIFICATE-----\n";
WiFiClientSecure client;
client.setCACert(rootCA);
Download the root certificate from the site's certificate details in your browser. Remember that certificates expire, so a sketch that works today can fail years from now with a connection error, and the ESP32 also needs the correct time to validate dates (set it with NTP using configTime()).
Redirects. Some services answer with a 301 or 302 redirect. Tell HTTPClient to follow them:
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
POST Request: Sending JSON
To send sensor data, build a JSON body and POST it with the right Content-Type header. This example posts to httpbin.org, a free testing service that echoes back what it receives, so you can see exactly what the ESP32 sent.
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
void postReading(float temperature, float humidity) {
WiFiClientSecure client;
client.setInsecure();
HTTPClient http;
http.begin(client, "https://httpbin.org/post");
http.addHeader("Content-Type", "application/json");
JsonDocument doc;
doc["device"] = "esp32-garage";
doc["temperature"] = temperature;
doc["humidity"] = humidity;
String body;
serializeJson(doc, body);
int code = http.POST(body);
Serial.printf("POST code: %d\n", code);
if (code > 0) {
Serial.println(http.getString());
}
http.end();
}
httpbin returns your request in its response, including the JSON you sent under "json". That makes it a good way to confirm headers and body are correct before pointing the code at your real server.
Form-encoded POST (what a classic HTML form sends):
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
int code = http.POST("temperature=24.5&humidity=60");
Headers, API Keys and Authentication
Services identify you with a key. Send it in a header, not in the URL, when the API allows it.
http.addHeader("Authorization", "Bearer YOUR_API_TOKEN");
http.addHeader("X-API-Key", "YOUR_KEY");
http.addHeader("Accept", "application/json");
For HTTP Basic authentication:
http.setAuthorization("username", "password");
Keep keys out of public code. Store them in a secrets.h file excluded from version control. If a key ever ends up on GitHub, revoke it and create a new one.
Do Not Block Your Loop, and Do Not Spam
A request takes anywhere from 100 ms to many seconds. Inside loop(), call it on a timer instead of continuously:
unsigned long lastRequest = 0;
const unsigned long INTERVAL = 60000; // once a minute
void loop() {
if (millis() - lastRequest >= INTERVAL) {
lastRequest = millis();
if (WiFi.status() == WL_CONNECTED) {
postReading(readTemperature(), readHumidity());
}
}
}
Respect API rate limits (many free tiers allow one request per minute or fewer), and always check that WiFi is up before sending. Add http.setTimeout(8000) so a dead server cannot freeze the device for long. Never run an HTTP request from an interrupt handler.
Common Problems and Fixes
Code is -1 (connection refused). The ESP32 could not reach the host. Check that WiFi is connected, that the URL and port are correct, and that DNS works (a static IP without DNS breaks this, see the static IP guide).
Code is -11 (read timeout). The server did not answer in time. Increase http.setTimeout(), or the server or your network is down.
HTTPS works on a phone but not on the ESP32. The ESP32 needs the right time and a valid root certificate, or use setInsecure() for testing.
Code 301 or 302, empty payload. Enable setFollowRedirects().
Code 400 on a POST. The body or the Content-Type header is wrong. Test the same request with httpbin to see what is being sent.
Crashes or code -8 with big responses. getString() loads everything into RAM. Parse from the stream instead (deserializeJson(doc, http.getStream())) or filter the JSON.
It works for a few hours, then stops. You forgot http.end(), or WiFi dropped and was never restored. Add reconnection logic from the WiFi tutorial.
Random reboots during requests. Weak power supply. WiFi transmit bursts need a solid 5V supply.
Frequently Asked Questions
Should I use HTTP or MQTT for sensor data? Use HTTP for occasional posts to web services and APIs. Use MQTT for frequent updates and two-way control, because it keeps one connection open and uses far less overhead per message.
Can the ESP32 also receive HTTP requests? Yes, as a web server. See the REST API tutorial to expose your sensors and relays as endpoints.
Does HTTPClient support PUT and DELETE? Yes: http.PUT(body) and http.sendRequest("DELETE").
Can I download a file? Yes. Use http.getStream() and write the stream to flash or an SD card in chunks, so you never hold the whole file in RAM.
Do I need the Arduino JSON library? Not for tiny responses, but ArduinoJson is the safest way to read and write JSON. It is far less error-prone than string searching.
What is the best interval for posting data? One to five minutes is plenty for temperature, and every few seconds only if you really need it. Longer intervals also let you use deep sleep to save battery.