My ESP32 keeps returning a HTTP 400 after trying POST to my Google Apps Script. Tried the same logic and parameters but to an ESP8266 and it worked fine. So this must be an error related to ESP32’s code.
bool uploadQueueToServer() {
if (WiFi.status() != WL_CONNECTED) return false;
if (queueCount == 0) { uploadPending = false; return true; }
Serial.println("=== UPLOAD QUEUE (" + String(queueCount) + " records) ===");
lastUploadAttempt = millis();
File f = LittleFS.open(FILE_QUEUE, "r");
if (!f) return false;
String body;
body.reserve(64 + queueCount * 120);
body = "{\"action\":\"batchMarkAttendance\",\"apiKey\":\"";
body += String(deviceApiKey);
body += "\",\"deviceId\":\""; body += String(deviceId); body += "\",\"logs\":[";
QueueRec rec; bool first = true; int count = 0;
while (f.read((uint8_t*)&rec, QUEUE_REC_SIZE) == QUEUE_REC_SIZE) {
if (!first) body += ","; first = false;
body += "{\"timestamp\":"; body += rec.timestamp;
body += ",\"cardUID\":\""; body += String(rec.cardUID); body += "\"";
body += ",\"studentId\":\""; body += String(rec.studentId);body += "\"";
body += ",\"name\":\""; body += String(rec.name); body += "\"";
body += ",\"class\":\""; body += String(rec.cls); body += "\"";
body += ",\"status\":\""; body += (rec.status ? "IN" : "OUT"); body += "\"}";
count++; yield();
}
f.close(); body += "]}";
HTTPClient http;
http.begin(wifiClient, scriptUrl);
http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
http.addHeader("Content-Type", "application/json");
http.setTimeout(25000);
int code = http.POST(body);
String response = http.getString();
http.end();
Serial.println("Upload HTTP: " + String(code));
if (code <= 0) return false;
StaticJsonDocument<512> rDoc;
if (deserializeJson(rDoc, response) != DeserializationError::Ok) return false;
if (!rDoc["success"].as<bool>()) return false;
clearQueue();
lastBatchUpload = millis();
return true;
}
Solved it. The first issue was reusing a single WiFiClientSecure across redirects, ESP32 breaks the TLS session when the redirect switches hosts, ESP8266 handles it internally. The second was that Google Apps Script redirects must be followed with GET, not POST, the script.googleusercontent.com/macros/echo URL is a content-delivery endpoint with no POST handler, so re-POSTing returns 405. Fixed both by disabling the built-in redirect follower and handling it manually with a fresh WiFiClientSecure per hop and a GET on all redirect hops.
Well I couldn’t explain that well in English, so I just made it translate using AI.
Redirect was 302 or a Moved Temporarily, the problem was Google App Script was successfully doing it's job, just not returning any info or status, I had to manually redirect to the link using GET, I just did HTTPC_DISABLE_FOLLOW_REDIRECTS since I could just get the link and do the redirect myself.
Thanks for the update. For those keeping score, the older, lower-number redirects -- 301, 302, and 303 -- generally convert to GET, while the newer 307 and 308 preserve the method. Again, ESP32 HTTPClient does not conform, always preserving the method with _FORCE_