Post Request on ESP32 to Google Apps Script

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;

}

As a web server? Unless you managed to squeeze Google onto there, that doesn't prove that much.

Try printing the response body that was read from the server. It may have specifics for why it considers it a Bad Request.

BTW, minor point: seems like having both first and count is redundant. It could just be

  if (count) body += ",";

or even

  if (count++) body += ",";

and don't increment at the end.

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.

Which kind of redirect was it? This AI-generated summary looks OK at first glance.

Summary Table of Common Redirects

Status Code Type Method Handling Common Use Case
303 See Other Temporary Changes to GET After a POST form submission (PRG pattern)
307 Temporary Redirect Temporary Preserves original method Temporary service changes where data must be maintained
301 Moved Permanently Permanent Changes to GET(by convention) Permanent URL changes for SEO
308 Permanent Redirect Permanent Preserves original method Permanent API endpoint changes with POST requests

However, HTTPC_FORCE_FOLLOW_REDIRECTS does not conform

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_

Here is the relevant part of the Fetch Standard: