troubleshooting · · 3 min read

Fix Google Apps Script Webhook 302 Redirect Error

Why your Telegram bot webhook returns 302 Moved Temporarily and how to fix it with one line change.

If your Google Apps Script webhook keeps returning 302 Moved Temporarily error and your Telegram bot refuses to respond, you’re probably using the wrong response method. Here’s the fix.

The Problem 🔍

When setting up a Telegram bot webhook with Google Apps Script, you might see this in your webhook info:

Last error: Wrong response from the webhook: 302 Moved Temporarily

Your doPost() function runs, but Telegram never gets a proper response. The pending updates keep piling up, and your bot stays silent.

The Cause

The culprit is ContentService.createTextOutput(). While it works for many GAS use cases, it causes redirect issues when used as a webhook response for external services like Telegram.

// ❌ This causes 302 redirect
function doPost(e) {
  // ... your logic
  return ContentService.createTextOutput("OK");
}

The Fix ✅

Replace ContentService.createTextOutput() with HtmlService.createHtmlOutput():

// ✅ This works
function doPost(e) {
  // ... your logic
  return HtmlService.createHtmlOutput("OK");
}

That’s it. One line change.

Complete Working Example

Here’s a minimal working doPost() for a Telegram webhook:

function doPost(e) {
  try {
    const contents = JSON.parse(e.postData.contents);
    const chatId = String(contents.message.chat.id);
    const text = (contents.message.text || "").toLowerCase().trim();

    // Your bot logic here
    if (text === "/ping") {
      sendMessage(chatId, "pong!");
    }

    return HtmlService.createHtmlOutput("OK");
  } catch (error) {
    return HtmlService.createHtmlOutput("Error");
  }
}

function doGet(e) {
  return HtmlService.createHtmlOutput("OK");
}

After Fixing

  1. Save your script (Ctrl+S)
  2. DeployManage deployments → click the pencil icon
  3. Set Version to New version
  4. Click Deploy
  5. Clear stuck pending updates:
https://api.telegram.org/bot<TOKEN>/setWebhook?url=<URL>&drop_pending_updates=true

How to Check Webhook Info 🛠️

Via Browser

Open this URL in your browser (replace <TOKEN> with your bot token):

https://api.telegram.org/bot<TOKEN>/getWebhookInfo

Via GAS Function

Add this function to your script and run it:

function getWebhookInfo() {
  const TELEGRAM_BOT_TOKEN = "YOUR_BOT_TOKEN";
  const url = "https://api.telegram.org/bot" + TELEGRAM_BOT_TOKEN + "/getWebhookInfo";

  const response = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
  const result = JSON.parse(response.getContentText());

  Logger.log("URL: " + (result.result.url || "(not set)"));
  Logger.log("Pending updates: " + result.result.pending_update_count);
  Logger.log("Last error: " + (result.result.last_error_message || "(none)"));
}

What the Error Looks Like

When the 302 error occurs, your webhook info response will look like this:

{
  "ok": true,
  "result": {
    "url": "https://script.google.com/macros/s/xxxxx/exec",
    "has_custom_certificate": false,
    "pending_update_count": 7,
    "last_error_date": 1770214432,
    "last_error_message": "Wrong response from the webhook: 302 Moved Temporarily",
    "max_connections": 40,
    "ip_address": "142.250.77.174"
  }
}

Key indicators of the problem:

  • pending_update_count keeps increasing
  • last_error_message shows 302 Moved Temporarily

After the Fix

Once you deploy with HtmlService.createHtmlOutput(), the response should look clean:

{
  "ok": true,
  "result": {
    "url": "https://script.google.com/macros/s/xxxxx/exec",
    "has_custom_certificate": false,
    "pending_update_count": 0,
    "max_connections": 40,
    "ip_address": "142.250.77.174"
  }
}

No last_error_message and pending_update_count stays at 0 — that’s how you know it’s working.


Now your Telegram bot should respond properly. Happy automating! 🤖