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)
解決策の検討
従来の対処法
-
Firebase Cloud Functionsに移行
- GASを使わず、すべてCloud Functionsで処理
- スプレッドシート連携が複雑になる
-
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は初回起動時に数秒かかることがあります(コールドスタート)。これを軽減するには:
- 最小限の依存関係: 不要なパッケージを削除
- 軽量なNode.js 20: 最新のランタイムを使用
- 常時起動(有料): 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. ログはどこで確認できますか?
個人デプロイの場合:
- Firebaseコンソールを開く
- プロジェクトを選択
- 左メニューの「Functions」→「ログ」をクリック
-
console.logやconsole.errorの出力が確認できます
公開サービスの場合:
- ダッシュボードで最新50件のログをUIで確認可能
まとめ
LINE WebhookとGASの連携における302エラーとタイムアウト問題は、プロキシサーバーを介することで解決できます。
本記事で紹介した内容:
- プロキシサーバーのアーキテクチャ
- Cloud Functionsでの実装方法(完全なソースコード)
- 個人でデプロイする手順(5分で完了)
- 実装のポイント(即座のレスポンス、リダイレクト追従)
自分でデプロイする場合:
- Firebase無料枠で運用可能
- API Key不要のシンプル実装
- 記事のコードをそのままコピーして使用
- 5分でデプロイ完了
公開サービスを使う場合:
- 複数のGAS URLを管理したい
- WebダッシュボードでログUIが欲しい
- すぐに使い始めたい
どちらも同じ技術で動いているので、用途に応じて選択してください。
同様の課題を抱えている方の参考になれば幸いです。
参考資料
関連記事:
公式ドキュメント:
- LINE Messaging API公式ドキュメント
- Google Apps Script公式ドキュメント
- Firebase Cloud Functions公式ドキュメント
- Google Apps Script - Webアプリケーションの作成
公開サービス: https://webhook-proxy-508a3.web.app
ソースコード: https://github.com/(公開予定)
ライセンス: MIT(予定) - 自由に改変・再配布可能