0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LINE Webhook GAS 302エラー・タイムアウト解決

Last updated at Posted at 2025-11-13

LINE Webhook + GAS で302エラーとタイムアウトを解決する方法

はじめに

LINE Messaging APIのWebhookをGoogle Apps Script(GAS)で受け取る際、302リダイレクトエラー3秒タイムアウトの問題に直面することがあります。

本記事では、これらの問題の原因と解決策を解説し、プロキシサーバーの実装方法を紹介します。

急いでいる方向けに、本記事で紹介する実装を公開サービスとして提供しています。
自分で実装するのが面倒な場合はこちらをどうぞ: https://webhook-proxy-508a3.web.app

問題の詳細

問題1: 302リダイレクト

GASのWebアプリURLは、以下のようにリダイレクトを返します:

https://script.google.com/macros/s/SCRIPT_ID/exec
  ↓ 302 Found
https://script.googleusercontent.com/...

LINE Messaging APIのWebhookはリダイレクトに対応していないため、以下のエラーが発生します:

Error: Webhook URL returned an error (HTTP 302)

問題2: 3秒タイムアウト

LINE WebhookはHTTPレスポンスを3秒以内に返す必要があります。GASでスプレッドシートへの書き込みなど重い処理を行うと、この制限を超えてしまいます。

Webhook delivery failed (Timeout)

解決策の検討

従来の対処法

  1. Firebase Cloud Functionsに移行

    • GASを使わず、すべてCloud Functionsで処理
    • スプレッドシート連携が複雑になる
  2. GASで非同期処理

    • LockServiceなどを使った工夫
    • 完全な解決にはならない

採用したアプローチ

プロキシサーバーを介した転送方式を採用しました。

LINE Webhook
  ↓
Proxy Server (Cloud Functions)
  ↓ 即座に200を返却
  ↓ 非同期でGASへ転送(リダイレクト追従)
  ↓
GAS(スプレッドシート処理)

この方式により:

  • Cloud Functionsが即座に200 OKを返すため、タイムアウトを回避
  • プロキシがリダイレクトを自動追従するため、302エラーを解決
  • GASでスプレッドシート操作を継続できる

実装方法

アーキテクチャ

Firebase Cloud Functionsでプロキシサーバーを実装します。

LINE Webhook
  ↓
Proxy Server (Cloud Functions)
  ↓ 即座に200を返却
  ↓ 非同期でGASへ転送(リダイレクト追従)
  ↓
GAS(スプレッドシート処理)

Cloud Functionsのコード

実装方法(私のアプリを使う場合は読み飛ばしてください):

import * as functions from 'firebase-functions';
import axios from 'axios';

export const webhookProxy = functions
  .region('us-central1')
  .https.onRequest(async (req, res) => {
    // CORS設定
    res.set('Access-Control-Allow-Origin', '*');
    
    if (req.method === 'OPTIONS') {
      res.set('Access-Control-Allow-Methods', 'GET, POST');
      res.set('Access-Control-Allow-Headers', 'Content-Type');
      res.status(204).send('');
      return;
    }

    const targetUrl = req.query.url as string;

    if (!targetUrl) {
      res.status(400).json({ 
        error: 'Missing required parameter: url' 
      });
      return;
    }

    // 即座に200を返却
    res.status(200).json({ success: true, message: 'Webhook received' });

    // 非同期でGASへ転送
    try {
      await axios({
        method: req.method,
        url: targetUrl,
        data: req.body,
        headers: {
          'Content-Type': req.headers['content-type'] || 'application/json',
        },
        maxRedirects: 5, // リダイレクト自動追従
        timeout: 60000,
      });

      console.log('Success:', { url: targetUrl, status: 'forwarded' });

    } catch (error: any) {
      console.error('Error:', { url: targetUrl, error: error.message });
    }
  });

特徴

  • URLパラメータで転送先を指定するだけ
  • ログはCloud Functionsのコンソールで確認可能

GAS側のコード

function doPost(e) {
  try {
    const json = JSON.parse(e.postData.contents);
    const events = json.events || [];
    
    const sheet = SpreadsheetApp
      .getActiveSpreadsheet()
      .getSheetByName("ログ");
    
    events.forEach(event => {
      if (event.type === "message") {
        sheet.appendRow([
          new Date(),
          event.source.userId,
          event.message.text
        ]);
      }
    });
    
    return ContentService
      .createTextOutput(JSON.stringify({ success: true }))
      .setMimeType(ContentService.MimeType.JSON);
  } catch (error) {
    return ContentService
      .createTextOutput(JSON.stringify({ 
        success: false, 
        error: error.toString() 
      }))
      .setMimeType(ContentService.MimeType.JSON);
  }
}

デプロイ手順

1. Firebaseプロジェクトの作成

npm install -g firebase-tools
firebase login
firebase init functions

プロジェクト作成時の選択:

  • TypeScriptを選択
  • ESLintは任意
  • 依存関係のインストール: Yes

2. 必要なパッケージのインストール

cd functions
npm install axios

3. functions/src/index.tsを編集

上記のCloud Functionsコードをfunctions/src/index.tsに貼り付けます。

4. デプロイ

firebase deploy --only functions

デプロイ完了後、以下のようなURLが表示されます:

Function URL (webhookProxy): https://us-central1-YOUR_PROJECT_ID.cloudfunctions.net/webhookProxy

このURLをメモしておきます。

使い方

デプロイしたCloud FunctionsのURLにGASのURLをパラメータとして付けます:

https://us-central1-YOUR_PROJECT_ID.cloudfunctions.net/webhookProxy?url=YOUR_GAS_URL

https://us-central1-my-project.cloudfunctions.net/webhookProxy?url=https://script.google.com/macros/s/ABC.../exec

このURLをLINE Developers Consoleの「Messaging API設定」→「Webhook URL」に設定します。

これだけで完了です!

実装のポイント

1. 即座にレスポンスを返す

// 即座に200を返却
res.status(200).json({ success: true, message: 'Webhook received' });

// 非同期でGASへ転送(awaitしない)
axios({ /* ... */ }).then(/* ... */).catch(/* ... */);

LINE Webhookは3秒以内にレスポンスが必要なため、GASへの転送を待たずに即座に200を返します。

2. リダイレクトの自動追従

await axios({
  url: targetUrl,
  maxRedirects: 5, // 重要: GASの302を自動追従
  // ...
});

axiosのmaxRedirectsオプションでリダイレクトを自動追従します。

3. シンプルなログ出力

console.log('Success:', { url: targetUrl, status: 'forwarded' });
console.error('Error:', { url: targetUrl, error: error.message });

Cloud FunctionsのログはFirebaseコンソールの「Functions」→「ログ」で確認できます。

(参考)公開サービスについて

上記の実装をベースに、ログ機能や使用量管理を追加した公開サービスも提供しています。

公開サービスの追加機能

  • 複数のGAS URLを登録・管理可能
  • Webダッシュボードでログ確認(最新50件)
  • 月次使用量の可視化
  • Firebase Authenticationによる認証

個人でデプロイする場合との違い

  • 個人デプロイ: シンプル、無料、自分だけが使える
  • 公開サービス: 複数URL管理、ログUI、アカウント管理あり

どちらも同じ技術で動いているので、まずは個人デプロイで試してみて、必要に応じて公開サービスに移行することもできます。

技術的な補足

なぜaxiosを使うのか

Node.jsのfetchやネイティブのhttpモジュールでも実装可能ですが、axiosは:

  • リダイレクト追従が簡単(maxRedirectsオプション)
  • タイムアウト設定が明確
  • エラーハンドリングが統一的

という理由で採用しています。

Cloud Functionsのコールドスタート対策

Cloud Functionsは初回起動時に数秒かかることがあります(コールドスタート)。これを軽減するには:

  1. 最小限の依存関係: 不要なパッケージを削除
  2. 軽量なNode.js 20: 最新のランタイムを使用
  3. 常時起動(有料): Cloud Runへの移行も検討可能

コストについて

個人デプロイの場合、Firestoreは不要です(上記のシンプル実装)。

公開サービスのように複数ユーザーで使う場合のみ、ログ保存にFirestoreを使います。

Cloud Functionsだけなら、無料枠で十分運用可能:

  • 呼び出し: 2,000,000回/月
  • GB秒: 400,000 GB秒/月
  • CPU秒: 200,000 GHz秒/月

月数百〜数千リクエスト程度なら完全に無料枠内です。

動作確認とテスト

curlでのテスト

curl -L \
  -H "Content-Type: application/json" \
  -d '{
    "events": [{
      "type": "message",
      "source": {"userId": "U1234567890abcdef"},
      "message": {"type": "text", "text": "テスト"}
    }]
  }' \
  "https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec"

注意: -X POST-Lを同時に使用すると、リダイレクト後にGETに変わる可能性があります。-dオプションのみで十分です。

検証済み環境

動作確認済み:

  • LINE Messaging API ✅

理論上対応(未検証):

  • Slack Webhook
  • Stripe Webhook
  • Discord Webhook
  • GitHub Webhook

リダイレクトやタイムアウトが問題になるWebhook全般で使えるはずです。

制限事項と注意点

Cloud Functionsの制限

  • タイムアウト: 最大60秒(第2世代Functionsは9分まで可能)
  • リクエストサイズ: 10MB(HTTPリクエストの制限)
  • 同時実行: デフォルト3000(設定変更可能)

セキュリティ上の注意

個人デプロイの場合

  • URLを知っている人なら誰でも使えてしまう
  • 自分だけが使うなら問題なし

トラブルシューティング

Q1. HTTP 302エラーが発生する

原因: GASのWebアプリURLが直接指定されている
解決: プロキシURLを使用する

Q2. タイムアウトが発生する

原因: GASの処理が3秒を超えている
解決: プロキシが即座に200を返すため、GASの処理時間は無関係

Q3. ログはどこで確認できますか?

個人デプロイの場合

  1. Firebaseコンソールを開く
  2. プロジェクトを選択
  3. 左メニューの「Functions」→「ログ」をクリック
  4. console.logconsole.errorの出力が確認できます

公開サービスの場合

  • ダッシュボードで最新50件のログをUIで確認可能

まとめ

LINE WebhookとGASの連携における302エラーとタイムアウト問題は、プロキシサーバーを介することで解決できます。

本記事で紹介した内容:

  • プロキシサーバーのアーキテクチャ
  • Cloud Functionsでの実装方法(完全なソースコード)
  • 個人でデプロイする手順(5分で完了)
  • 実装のポイント(即座のレスポンス、リダイレクト追従)

自分でデプロイする場合:

  • Firebase無料枠で運用可能
  • API Key不要のシンプル実装
  • 記事のコードをそのままコピーして使用
  • 5分でデプロイ完了

公開サービスを使う場合:

  • 複数のGAS URLを管理したい
  • WebダッシュボードでログUIが欲しい
  • すぐに使い始めたい

どちらも同じ技術で動いているので、用途に応じて選択してください。

同様の課題を抱えている方の参考になれば幸いです。

参考資料

関連記事:

公式ドキュメント:


公開サービス: https://webhook-proxy-508a3.web.app
ソースコード: https://github.com/(公開予定)
ライセンス: MIT(予定) - 自由に改変・再配布可能

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?