ボットサーバーの戻り値に使用するオブジェクトについて
WebアプリではdoGet関数かdoPost関数を使用し、HTMLサービスのHtmlOutputオブジェクトかContentサービスのTextOutputオブジェクトを返す必要がある。
Webhookの戻り値としてContentサービスを使用して戻り値を作成すると、GASの仕様上リダイレクトが発生し、LINE側にはエラーとして扱われる。
レスポンスコードが「200 OK」であればエラーとならないため、ここではHTMLサービスを使用して戻り値を返す。
doPost(e) { // リダイレクトにより「302 Found」となる return ContentService.createTextOutput('OK')}
Contentサービスを使用した戻り値を設定した場合、公式ドキュメントに記載されているようにリダイレクトをするため、「302 Found」がレスポンスコードとして返ってくる。
検証ボタンでは「302 Found」となり、運用時もエラーと見なされる。
doPost(e) { // リダイレクトは発生せず、「200 OK」となる return HtmlService.createHtmlOutput('OK')}
Htmlサービスを使用して戻り値を設定した場合、「200 OK」がレスポンスコードとして返ってくる。
レスポンスコードのみで判断しているので中身については自由に記述しても問題ない。
Htmlサービス以外にnull、戻り値なし、戻り値指定しない場合でも「200 OK」となることを確認しているが、Google側の仕様変更によってレスポンスコードが変わる可能性もある。
doPost(e) { // nullとした場合、「スクリプトが完了しましたが、何も返されませんでした。」と表示され、「200 OK」となる return null}
ユーザーからのメッセージの取りこぼしをを防ぐために「再送をする」をオンにしていた場合、2秒以内に「200 OK」のレスポンスコードを返す必要がある。
実際の処理をdoPost関数で行った場合、2秒を超過する。
「再送する」をオンにする場合、以下のようにトリガーを使用した非同期処理で対応する。
トリガーを使い、先にレスポンスを返し、後追いで実際の処理を行うように実装する。
1の処理でレスポンスに2秒以上かかる場合もあるが2度目以降では重複チェックにより即座にレスポンスを返すため再送されない。
※実際には2秒以内にレスポンスを返せず、2回目、3回目、…とリトライされることもあります。
1-1.WebhookEventIDによる重複チェック
既に受信している場合は処理終了
1-2.ローディング表示(最大60秒)
1-3.2.実際の処理を行うトリガーを作成
1-4.WebhookEventIDに受信内容を紐づけてキャッシュサービスに保存
1-4.トリガーIDにユーザーID/グループIDを紐づけてキャッシュサービスに保存
1-5.ユーザーID/グループID単位でWebhookEventIDをキューとしてキャッシュサービスに保存
1-6.処理終了
2-1.トリガーからの起動であるかチェック
2-2.トリガーIDに紐づくユーザーID/グループIDを取得
2-3.ユーザーID/グループIDに紐づくキューからWebhookEventIDを順に取得し処理を行う
2-4.実際の処理を行う(サンプルではオウム返し)
2-5.キャッシュサービスに保存した内容を削除
2-6.自トリガーを削除※1スクリプト1ユーザーあたり最大で20個までのため、必須
2-7.処理終了
トリガーを使用した非同期処理を行うことでGAS単体でもLINE Webhookのボットサーバーを構築することができるが以前として以下a-cの問題点が存在する。
a.リクエストヘッダーを読み込めないため、
LINEの公式ドキュメントに記載されている署名検証が行えない
b.コールドスタートによりレスポンスを2秒以内に返せないことがある
c.トリガーが最大20個までしか作成できない
※Googleサービスの割り当てを参照
d.トリガーを使用した非同期処理の場合、
推奨されている「応答トークン」の「1分以内の使用」がほぼ不可能
※2026年06月時点では20分まで使用できている
上記の問題点を解決するため、PythonAnywhere上で python、flask で中継サーバーを構築する。
1.署名検証を行う。
2.GASへ受信内容を渡す。(※非同期処理)
3.LINEに「200 OK」のレスポンスコードを返す。
実際のコードは「note」にて公開予定です。