How to connect Discord bot with Excel Online using Apps Script

Hello everyone! I’m a newcomer here, so I appreciate your patience if I make any mistakes with my question. I’m trying to create a Discord bot that captures messages from my Discord server and saves this information into an online Excel spreadsheet. I’m utilizing Google Apps Script for this task.

I have written some code that tests fine with the testDoPost function. The data is added to my spreadsheet successfully when testing. I’ve confirmed that everything is set up correctly - the webhook is activated, the web app is deployed, and all connections appear to function.

However, the strange issue is that when I send a message in Discord, the bot doesn’t respond at all. Instead, I receive this error message: “last_error_message”: “Wrong response from the webhook: 302 Moved Temporarily”

I have attempted all sorts of solutions. I deleted the bot and created it again, removed the webhook, and then re-established it. I’ve even redeployed the web app repeatedly with no success, which is making me quite frustrated.

Here’s my current code (I’ve removed the actual tokens and URLs for security purposes):

const SPREADSHEET_NAME = 'MyData';

function storeDiscordToken() {
  const interface = SpreadsheetApp.getUi();
  const prompt = interface.prompt('Enter Discord Token', 'Type your Discord bot token here:', interface.ButtonSet.OK_CANCEL);
  
  if (prompt.getSelectedButton() == interface.Button.OK) {
    const botToken = prompt.getResponseText().trim();
    if (botToken) {
      PropertiesService.getScriptProperties().setProperty('DISCORD_TOKEN', botToken);
      interface.alert('Token saved successfully!');
    } else {
      interface.alert('Please enter a valid token');
    }
  }
}

function getDiscordToken() {
  const savedToken = PropertiesService.getScriptProperties().getProperty('DISCORD_TOKEN');
  if (!savedToken) {
    throw new Error('Discord token missing. Run storeDiscordToken() first.');
  }
  return savedToken;
}

function doGet(request) {
  return HtmlService.createHtmlOutput("Bot webhook is running and ready to receive messages.");
}

function doPost(request) {
  try {
    console.log("Webhook triggered by Discord");
    
    if (!request || !request.postData || !request.postData.contents) {
      console.log("No data received from Discord");
      return ContentService.createTextOutput(JSON.stringify({ result: 'error', info: 'No data received' }))
                           .setMimeType(ContentService.MimeType.JSON);
    }
    
    console.log("Raw message data: " + request.postData.contents);
    
    const messageData = JSON.parse(request.postData.contents);
    console.log("Parsed message: " + JSON.stringify(messageData));
    
    if (!messageData.message || !messageData.message.text) {
      console.log("No text content found in message");
      return ContentService.createTextOutput(JSON.stringify({ result: 'error', info: 'No text found' }))
                           .setMimeType(ContentService.MimeType.JSON);
    }
    
    const textContent = messageData.message.text;
    const channelId = messageData.message.chat.id;
    console.log("Processing message: " + textContent);
    console.log("Channel ID: " + channelId);
    
    if (textContent.startsWith('!') && /\d/.test(textContent)) {
      const orderNumber = textContent.match(/!(\d+)/);
      const quantityMatch = textContent.match(/(\d+)x/);
      const priceMatch = textContent.match(/(\d+\.\d+)\$/);
      const storeMatch = textContent.match(/\$([a-zA-Z0-9]+)/);
      
      console.log("Extracted data: " + JSON.stringify({ orderNumber, quantityMatch, priceMatch, storeMatch }));
      
      if (orderNumber && quantityMatch && priceMatch && storeMatch) {
        const orderId = orderNumber[1];
        const quantity = quantityMatch[1];
        const price = priceMatch[1];
        const store = storeMatch[1];
        
        const dataSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SPREADSHEET_NAME);
        if (!dataSheet) {
          console.log(`Cannot find sheet: ${SPREADSHEET_NAME}`);
          replyToDiscord(channelId, `Error: Cannot find spreadsheet ${SPREADSHEET_NAME}`);
          return ContentService.createTextOutput(JSON.stringify({ result: 'error', info: 'Sheet not found' }))
                               .setMimeType(ContentService.MimeType.JSON);
        }
        
        dataSheet.appendRow([orderId, quantity, price, store]);
        console.log("Successfully added data to spreadsheet");
        
        replyToDiscord(channelId, 'Order data saved successfully!');
      } else {
        console.log("Message format is incorrect");
        replyToDiscord(channelId, 'Wrong format. Use: !order quantityx price$store');
      }
    } else {
      console.log("Message does not match expected pattern");
      replyToDiscord(channelId, 'Invalid format. Use: !order quantityx price$store');
    }
    
    return ContentService.createTextOutput(JSON.stringify({ result: 'success' }))
                         .setMimeType(ContentService.MimeType.JSON);
                         
  } catch (err) {
    console.log("Error processing request: " + err.message);
    return ContentService.createTextOutput(JSON.stringify({ result: 'error', info: err.message }))
                         .setMimeType(ContentService.MimeType.JSON);
  }
}

function replyToDiscord(channelId, messageText) {
  const BOT_TOKEN = getDiscordToken();
  const apiUrl = `https://api.discord.com/bot${BOT_TOKEN}/sendMessage`;
  const requestData = {
    chat_id: channelId,
    text: messageText,
  };
  const requestOptions = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(requestData),
  };
  
  try {
    const apiResponse = UrlFetchApp.fetch(apiUrl, requestOptions);
    console.log("Discord reply sent: " + apiResponse.getContentText());
  } catch (err) {
    console.log('Failed to send Discord message: ' + err.message);
  }
}

function setupWebhook() {
  const BOT_TOKEN = getDiscordToken();
  const webAppEndpoint = 'https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec';
  const webhookUrl = `https://api.discord.com/bot${BOT_TOKEN}/setWebhook?url=${encodeURIComponent(webAppEndpoint)}`;
  
  try {
    const response = UrlFetchApp.fetch(webhookUrl);
    console.log('Webhook configured: ' + response.getContentText());
  } catch (err) {
    console.log('Webhook setup failed: ' + err.message);
  }
}

function testWebhook() {
  const mockRequest = {
    postData: {
      contents: JSON.stringify({
        message: {
          text: "!456 12x 34.56$testStore",
          chat: {
            id: 987654321
          }
        }
      })
    }
  };
  
  doPost(mockRequest);
}

I suspect the issue lies within my code as I used an AI tool to assist in writing it, but I’m unsure how to pinpoint the problem. Has anyone experienced this 302 error before? I would greatly appreciate any suggestions that might explain what could be going wrong.

you’re mixing up discord and telegram apis. discord webhooks don’t use message.text or chat.id - that’s telegram. for discord, you need content and channel_id in the payload. your replyToDiscord function is calling telegram endpoints, not discord ones. that 302 error usually means permissions - check that your webapp allows anonymous access.

The 302 error means your web app isn’t set for public access. When you deploy, set ‘Execute as: Me’ and ‘Who has access: Anyone’ - otherwise Discord can’t reach your endpoint. I had this exact same issue. Discord expects a 200 response immediately, but you’re trying to send back JSON. Discord webhooks don’t need complex return values - just a simple 200 status. Also check your web app URL - it should end with ‘/exec’ not ‘/dev’. The dev URL changes every time you redeploy, which breaks the webhook. Use the production URL that stays constant. Try simplifying your doPost function to just return HtmlService.createHtmlOutput(‘OK’) at the end instead of JSON responses. Discord doesn’t care about the response content, just that it gets a successful HTTP status.

I hit the same 302 error working on a Discord bot last year. Your code’s mixing Discord’s API with Telegram’s - they’re completely different.

That 302 redirect happens when Google Apps Script handles auth wrong or when the response format doesn’t match what Discord expects. Discord wants specific webhook responses, not the JSON you’re sending back.

For Discord bots, you need Discord’s actual webhook format or slash commands. Your message structure should grab content, not message.text. Channel ID comes from channel_id, not message.chat.id. Plus your replyToDiscord function is using Telegram’s endpoints instead of Discord’s REST API.

I’d start with Discord’s official webhook docs or just use slash commands - they’re way easier for what you’re trying to do.