AI AGENT FACTORYAIエージェント工場見学 ACTIVE

LINE Botに月額費用はかからない──Cloudflare Workers無料枠で本番運用できる理由と構築手順

LINE Botを作ろうとしたとき、最初に悩むのが「サーバーをどこに置くか」問題です。VPSを借りれば月額880円〜、AWSはスタートは無料でも運用が続くとコストが積み上がる。有料のBotツールなら月額数千〜数万円。しかしCloudflare Workersの無料プランは、1日あたり10万リクエストまで0円で動き続けます。この記事では、なぜCloudflare Workersが個人開発者のLINE Bot構築に向いているのかを整理したうえで、実際に動くBotを最短で構築する手順を解説します。


仕組みの全体像(図解)

flowchart LR
    U[ユーザー] -->|メッセージ送信| LP[LINE Platform]
    LP -->|Webhook POST| CW[Cloudflare Workers]
    CW -->|署名検証| SV{検証OK?}
    SV -->|NG| ER[401 拒否]
    SV -->|OK| EV[イベント処理]
    EV -->|オプション| AI[AI API\nClaude / OpenAI]
    EV -->|オプション| DB[Cloudflare D1\nKV ストレージ]
    AI --> REP[返信生成]
    DB --> REP
    EV --> REP
    REP -->|Reply API| LP
    LP -->|メッセージ配信| U

LINEユーザーがメッセージを送ると、LINEのサーバーがCloudflare WorkersのURLにPOSTリクエストを送ります(Webhook)。Workersは署名を検証して正規のリクエストかを確認し、返信メッセージをLINE Reply APIに送って完了です。この一連の流れにサーバー管理は一切不要です。


なぜ「月額0円」が成り立つのか──Cloudflare Workers無料プランの実力

「無料枠なんてすぐ枯渇するでしょ」という疑問は正しい問いかけです。実際の数字を見てみましょう。

無料プランの制限値

項目無料プラン目安
リクエスト数10万req / 日1日あたり約69req/分
CPU時間10ms / リクエスト軽量処理に十分
メモリ128MBテキストBot用途は余裕
Workers数100個まで複数Bot運用も可能
月額料金0円クレカ登録不要

LINE Botの用途で「10万req/日」がどのくらいかを考えてみます。1メッセージにつき1リクエストなので、単純計算で1日10万件のLINEメッセージまで処理できます。個人や小規模事業者の問い合わせBotであれば、月間数千件程度が一般的ですから、無料枠が枯渇することはほぼありません。

CPU時間10msの制約は実際に問題になるか

ここは正直に書きます。通常のテキスト返信Botであれば10ms制限は問題になりません。署名検証→JSONパース→Reply API呼び出し、この処理はCPU時間として数ms以下で完了します。

注意が必要なのはAI APIを同期的に呼び出すパターンです。外部APIへのfetch呼び出し中は待機状態となり、その待機時間はCPU時間にカウントされません。ただし、waitUntil()を使わずにリクエストハンドラー内でAI APIの応答を待ち続けると、LINEのWebhookタイムアウト(数秒)に引っかかるケースがあります。この対策は後述する実装で解説します。


他の選択肢と比べてどうか──4つのプラットフォーム横断比較

「無料ならCloudflareでいい」と即断する前に、選択肢を並べて比較します。判断の参考にしてください。

Cloudflare WorkersVPS(さくら等)AWS LambdaVercel
月額費用0円880円〜使用量課金(低コスト)無料〜$20
無料枠10万req/日なし100万req/月商用利用制限あり
サーバー管理不要必要(OS・SSL等)不要不要
コールドスタートほぼなし(V8エンジン)なしあり(数百ms〜)あり
設定難易度
Node.jsそのまま△(Web標準API)
LINE Bot向き度

Cloudflare WorkersがLINE Bot用途に特に向いている理由はコールドスタートがほぼない点です。AWS Lambdaはリクエストがない時間が続くと起動に数百ms〜かかることがあり、Webhookのレスポンス速度に影響します。CloudflareのV8 Isolateは起動が高速で、LINEのWebhookタイムアウト内に確実に応答できます。

ただし、Cloudflare WorkersはNode.js APIをそのまま使えるわけではなく、ブラウザ互換のWeb標準API(fetchcrypto.subtle等)がベースです。fsモジュールや一部のNode.jsライブラリは動作しないため、Node.jsのコードをそのまま移植しようとするとハマります。


なぜLINE Bot用途に向いているのか──Webhookとサーバーレスはなぜマッチするのかサーバーレスの設計思想を理解する

LINE Messaging APIのWebhookは「LINEがHTTP POSTを投げてくる→Botが受け取って処理する」というイベント駆動型の仕組みです。これはサーバーレス関数の動作モデルと完全に一致します。

常時起動するサーバーが必要な理由がないのです。Webhookを受け取ったときだけ処理を走らせればいい。Cloudflare WorkersはまさにそのためのIaC(Infrastructure as Code)を持たない最小構成の実行環境です。

ステートレス制約とその回避策

サーバーレスの重要な制約として、リクエストをまたいで変数を保持できない(ステートレス)点があります。たとえば「前のメッセージで何を話したか」という会話履歴をWorkers内のグローバル変数に保存しようとしても、次のリクエストでは別のインスタンスが起動するため消えてしまいます。

会話履歴や設定値を永続化したい場合は以下を使います:

  • Cloudflare KV:キーバリューストア。無料プランで100,000回の読み取り/日
  • Cloudflare D1:SQLiteベースのデータベース。無料枠で5GBまで
  • 外部DB(Supabase等):fetch経由でアクセス可能

単純なオートリプライBotなら永続化は不要です。まずはステートレスで動く最小構成から始めましょう。


実際に動かす──最小構成での構築手順

前提:必要なもの

  • LINEアカウント(個人用で可)
  • Cloudflareアカウント(無料登録。クレカ不要)
  • Node.js 18以上(ローカル環境)
  • wrangler CLI(Cloudflareの公式ツール)

Step 1:LINE Messaging APIチャンネルの作成

  1. LINE Developers Console にLINEアカウントでログイン
  2. 「新規プロバイダー作成」→プロバイダー名を入力
  3. 「Messaging API」チャンネルを作成
  4. チャンネル設定画面で以下の2つをメモする:
  5. Channel Secret(「チャンネル基本設定」タブ)
  6. Channel Access Token(「Messaging API設定」タブ → 「チャンネルアクセストークン(長期)」を発行)

Webhookの設定はWorkerをデプロイしてURLが決まってからになります。この段階では空欄で構いません。

Step 2:Cloudflare Workersプロジェクトの初期化

# wrangler CLIのインストール
npm install -g wrangler

# Cloudflareアカウントにログイン
wrangler login

# プロジェクト作成("Hello World" テンプレートを選択)
npm create cloudflare@latest line-bot
cd line-bot

wrangler.tomlを確認します(自動生成されています):

name = "line-bot"
main = "src/index.js"
compatibility_date = "2024-09-23"

特に変更は不要ですが、nameはCloudflareダッシュボード上の表示名になります。好みの名前に変更しても構いません。

Step 3:署名検証とWebhookハンドラーの実装

src/index.jsを以下の内容で上書きします。これが最小構成の完全なコードです。

export default {
  async fetch(request, env, ctx) {
    // GETリクエストは拒否(LINEはPOSTのみ使用)
    if (request.method !== 'POST') {
      return new Response('Method Not Allowed', { status: 405 });
    }

    const body = await request.text();

    // 署名検証(必須。省略するとセキュリティホールになる)
    const signature = request.headers.get('x-line-signature');
    const isValid = await verifySignature(body, signature, env.CHANNEL_SECRET);
    if (!isValid) {
      return new Response('Unauthorized', { status: 401 });
    }

    const { events } = JSON.parse(body);

    // waitUntil: LINEへの200 OKを先に返してから非同期処理を続ける
    ctx.waitUntil(handleEvents(events, env));

    return new Response('OK', { status: 200 });
  },
};

// HMAC-SHA256 署名検証
async function verifySignature(body, signature, channelSecret) {
  if (!signature) return false;

  const encoder = new TextEncoder();
  const key = await crypto.subtle.importKey(
    'raw',
    encoder.encode(channelSecret),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign']
  );

  const signatureBytes = await crypto.subtle.sign(
    'HMAC',
    key,
    encoder.encode(body)
  );

  const computedSignature = btoa(
    String.fromCharCode(...new Uint8Array(signatureBytes))
  );

  return computedSignature === signature;
}

// イベント処理
async function handleEvents(events, env) {
  for (const event of events) {
    if (event.type === 'message' && event.message.type === 'text') {
      const userText = event.message.text;
      const replyText = `受け取りました:「${userText}」`;
      await replyMessage(event.replyToken, replyText, env.CHANNEL_ACCESS_TOKEN);
    }
  }
}

// LINE Reply API 呼び出し
async function replyMessage(replyToken, text, accessToken) {
  const response = await fetch('https://api.line.me/v2/bot/message/reply', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${accessToken}`,
    },
    body: JSON.stringify({
      replyToken,
      messages: [{ type: 'text', text }],
    }),
  });

  if (!response.ok) {
    const error = await response.text();
    console.error('Reply API error:', error);
  }
}

ctx.waitUntil()が重要な理由:LINEのWebhookはBotサーバーからすぐに200 OKが返ってこないとタイムアウトします。waitUntil()を使うことで「200 OKを先に返しつつ、処理をバックグラウンドで継続する」ことができます。AI APIのような遅い処理が入る場合は特に必須のパターンです。

Step 4:環境変数(シークレット)の安全な設定

Channel SecretとChannel Access Tokenをコードに直書きするのは絶対にNGです。wrangler secretコマンドで安全に設定します。

# Channel Secretを設定(実行後にプロンプトが出るので値を貼り付け)
wrangler secret put CHANNEL_SECRET

# Channel Access Tokenを設定
wrangler secret put CHANNEL_ACCESS_TOKEN

設定した値はCloudflareのサーバー側で暗号化保存され、Worker内ではenv.CHANNEL_SECRETのようにアクセスできます。.envファイルやwrangler.tomlに直接書いてGitにコミットしないよう注意してください。

ローカル開発時にテストしたい場合は、プロジェクトルートに.dev.varsファイルを作成します(.gitignoreに追加することを忘れずに):

CHANNEL_SECRET=ここにChannel Secretを貼る
CHANNEL_ACCESS_TOKEN=ここにChannel Access Tokenを貼る

Step 5:デプロイとWebhook URLの登録

# デプロイ(初回は少し時間がかかります)
wrangler deploy

デプロイ成功後、ターミナルにURLが表示されます:

✅ Deployed to https://line-bot.あなたのサブドメイン.workers.dev

このURLを使ってLINE DevelopersコンソールのWebhookを設定します。

  1. LINE Developers Consoleの「Messaging API設定」タブを開く
  2. 「Webhook URL」にhttps://line-bot.あなたのサブドメイン.workers.devを入力
  3. 「Webhookの利用」をオンにする
  4. 「検証」ボタンを押して「成功」が返ってくるか確認

「成功」が表示されれば設定完了です。LINEからBotにメッセージを送ってみて、オウム返しが届けば動作確認完了です。


AI応答への拡張パス──次のステップ

基本のオウム返しBotが動いたら、AI APIを繋ぎます。外部APIへのfetchはCPU時間にカウントされないため、Cloudflare Workersでも問題なく使えます。

// Claude APIを使った返信生成( Anthropic SDK不使用・素のfetch版)
async function generateAIReply(userText, apiKey) {
  const response = await fetch('https://api.anthropic.com/v1/messages', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': apiKey,
      'anthropic-version': '2023-06-01',
    },
    body: JSON.stringify({
      model: 'claude-haiku-4-5-20251001',
      max_tokens: 300,
      messages: [{ role: 'user', content: userText }],
    }),
  });

  const data = await response.json();
  return data.content[0].text;
}

wrangler secret put ANTHROPIC_API_KEYでAPIキーを設定し、handleEvents内でgenerateAIReplyを呼び出せば完成です。Claude APIのキーはAnthropic Consoleで取得できます(公式サイトを確認してください)。


よくあるエラーと対処

エラー1:「検証」ボタンで「失敗」と表示される

原因の8割はURL末尾のスラッシュ問題。 https://line-bot.xxx.workers.dev/のように末尾にスラッシュが入っているか、URLのコピーミスがほとんどです。

確認手順:

  1. WorkerのURLをwrangler deployの出力から直接コピーする
  2. LINE DevelopersコンソールのWebhook URLに貼り付け直す
  3. Workerが正常に起動しているかwrangler tailでログを確認する
# リアルタイムログ確認(別ターミナルで実行)
wrangler tail

エラー2:署名検証が常に失敗する(401が返り続ける)

最も多いのはCHANNEL_SECRETの設定ミスです。

確認手順:

  1. wrangler secret listCHANNEL_SECRETが存在するか確認
  2. LINE Developers ConsoleのChannel Secretをコピーし直してwrangler secret put CHANNEL_SECRETで再設定
  3. Channel Secret(チャンネル基本設定)とChannel Access Token(Messaging API設定)を取り違えていないか確認

また、リクエストボディをrequest.text()で読んだ後に再度request.json()で読もうとするとエラーになります。ボディは一度しか読めないため、上記のコード例のようにtext()で読んでからJSON.parse()するパターンを使ってください。

エラー3:ユーザーに返信が届かない(Workerのログにエラーがない)

Reply Tokenの有効期限切れが疑われます。Reply TokenはWebhookを受け取ってから30秒以内に使い切る必要があります。

wrangler tailでReply APIのレスポンスを確認します。400 Bad Requestが返っている場合はReply Tokenが期限切れです。重い処理(DBアクセス、AI API呼び出し等)で30秒を超えている可能性があります。AIの場合はPush Message API(非同期)への切り替えも検討してください(ただしPush APIは送信数上限があります。公式ドキュメントを確認してください)。

エラー4:ローカルでwrangler devを動かしてもLINEからWebhookが届かない

ローカルサーバー(localhost)にはLINEのサーバーからアクセスできません。テストするには以下のどちらかです:

  • wrangler deployで実際にデプロイしてテスト(本番環境だが即反映される)
  • ngrokなどのトンネリングツールでローカルを公開し、一時的にWebhook URLを変更する

エラー5:wrangler.tomlcompatibility_dateが古くてAPIが動かない

Cloudflare Workersのランタイムは日付ベースの互換性モードを採用しています。wrangler.tomlcompatibility_dateが古い場合、一部のWeb標準APIが使えないことがあります。compatibility_dateを現在の日付に更新し、再デプロイすると解決するケースがあります(変更の影響はCloudflareの公式ドキュメントで確認してください)。


まとめ──次の一歩

LINE BotをCloudflare Workersで動かすことで、サーバー管理ゼロ・月額費用ゼロで本番運用が成立します。「無料枠があるうちだけ動く」ではなく、個人や小規模事業者のユースケースでは継続的に無料で運用できる現実的な選択肢です。

今回の記事でカバーした内容を整理すると:

  • Cloudflare Workers無料プランは1日10万リクエストまで0円
  • LINE Bot(Webhook型)はサーバーレスとアーキテクチャが自然にマッチする
  • VPS・Lambda・Vercelと比べてもコスト・設定難易度・コールドスタートの面で優位
  • 署名検証は省略不可のセキュリティ要件
  • ctx.waitUntil()でWebhookタイムアウト問題を回避
  • シークレットはwrangler secret putで安全に管理

次の一歩として試せること:

  1. AI APIを繋いで「問い合わせに自動回答するBot」に発展させる
  2. Cloudflare KVで簡単なユーザー別設定を保存する
  3. LINE Flex Message(リッチなカードUI)で見た目を改善する

基本が動けば拡張は難しくありません。まずはオウム返しBotをデプロイするところから始めてください。

← 攻略ガイド一覧へ