Cloudflare WorkersでLINE Botを動かすのは、コピペで5分と言われる。これは正しい。ただし、Webhook URLを1文字でも間違えれば1時間が消え、シークレットキーの扱いを誤れば、動いているように見えて実は無防備な状態になる。この記事は、そういった詰まりを先に把握してから最短で動作確認まで辿り着くことを目的に書いた。手順の羅列ではなく「どこで止まるか」を先に見せる。
仕組みの全体像(図解)
まず全体のデータフローを把握する。詰まりポイントはこのフローのどこかに必ず現れる。
flowchart LR
A[ユーザー] --> B[LINEプラットフォーム]
B --> C[Webhook POST送信]
C --> D[Cloudflare Workers]
D --> E{署名検証}
E -->|不一致| F[401 Unauthorized]
E -->|一致| G[メッセージ処理]
G --> H[Reply API呼び出し]
H --> B
このフロー上で詰まりが起きやすい箇所は3か所だ。
- C(Webhook送信):URL設定ミスでWorkerに到達しない
- D〜E(Worker受信〜署名検証):シークレット管理のミスで認証が通らない
- H(Reply API):アクセストークンの設定ミスで返信が届かない
この3か所を先に押さえておくだけで、詰まったとき「どこを見ればいいか」がすぐ分かる。
Cloudflare Workersの無料枠、LINE Botに使えるか
先に結論を言う。使える。実用上は十分だ。
Cloudflare Workersの無料プランは2026年現在、1日10万リクエストまで無料とされている(制限値は変更される可能性があるため、最新情報はCloudflare公式ドキュメントで確認してほしい)。CPU時間は1リクエストあたり10ms。
LINE Bot用途で考えると:
- 1日10万リクエストは、アクティブユーザーが数百人規模でも余裕がある
- CPU時間10msはシンプルな署名検証とメッセージ処理には十分足りる
- コールドスタートがないため、LambdaやCloud Functionsより応答が安定しやすい
注意点が一つある。OpenAI APIやClaude APIなど外部LLMへの同期リクエストを挟むと、LINEの応答タイムアウト(約5秒)に引っかかる可能性がある。 AI返信を入れる場合は非同期処理(後述)が必要になる。
「無料で動くが、AI返信を素直に入れると詰まる」という点だけ最初に覚えておけばよい。
詰まりポイント1:Webhook URLの「検証ボタン」が通らない
LINE DevelopersコンソールでWebhook URLを登録し、「検証」ボタンを押すと「失敗しました」と出る——これが最初の壁になりやすい。
よくある原因と確認順序
(1)まだデプロイしていない
wrangler dev で起動したローカルサーバーは外部から届かない。LINE Platformからのリクエストを受けるには wrangler deploy でCloudflareにデプロイした後のURLが必要だ。ローカルで動作確認したい場合は wrangler dev --remote または ngrokなどのトンネルツールを使う。
(2)Workers URLのコピーミス
デプロイ後のURLは https://<Worker名>.<アカウント名>.workers.dev 形式になる。スペースや改行が混入していないか確認する。
(3)GETリクエストに対してエラーを返している
LINEの「検証」はPOSTを送る。しかしブラウザや一部のヘルスチェックはGETでアクセスしてくる。GETに500を返す実装になっていると混乱の元になるため、GETには素直に200を返しておく。
// POSTでなければ200を返すだけ
if (request.method !== 'POST') {
return new Response('OK', { status: 200 });
}
(4)カスタムドメインのHTTPS設定もれ
Workers.devのURLはデフォルトでHTTPSが有効だ。カスタムドメインを使う場合はSSL証明書の設定を確認する。
詰まりポイント2:シークレットを間違った場所に書いてしまう
LINE Botには2つのシークレットな値がある。
- チャネルシークレット(Channel Secret):署名検証に使う
- チャネルアクセストークン(Channel Access Token):メッセージ送信の認証に使う
これを wrangler.toml の [vars] セクションに書いてしまうミスが多い。
# ❌ やってはいけない
[vars]
CHANNEL_SECRET = "abc123..."
CHANNEL_ACCESS_TOKEN = "xyz..."
[vars] はソースコードと同様に平文でデプロイされ、GitHubにpushすれば丸見えになる。
正しい管理方法:wrangler secret put を使う
# ターミナルで実行。値は対話形式で入力する
wrangler secret put CHANNEL_SECRET
wrangler secret put CHANNEL_ACCESS_TOKEN
設定したシークレットはCloudflareが暗号化して保存し、Worker実行時に env.CHANNEL_SECRET としてアクセスできる。
ローカル開発では .dev.vars を使う
# .dev.vars(.gitignore に追加すること)
CHANNEL_SECRET=ここにチャネルシークレットを書く
CHANNEL_ACCESS_TOKEN=ここにアクセストークンを書く
.dev.vars は wrangler dev でのみ読み込まれ、デプロイには含まれない。
詰まりポイント3:署名検証を後回しにした代償
「まず動かしてみてから署名検証を追加する」と思って省略すると、誰でも偽のリクエストをWorkerに送れる状態になる。
悪意あるリクエストを大量に送られると:
- チャネルアクセストークンを使った返信が無限に発生し、LINEのAPIレート制限に引っかかる
- Cloudflare Workersの無料枠リクエスト数を消費する
実装自体は難しくない。ただし、Node.jsの crypto モジュールではなく Web Crypto API を使う点に注意が必要だ(詳細は次の詰まりポイントで説明する)。
async function verifySignature(body, signature, channelSecret) {
const enc = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
enc.encode(channelSecret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const mac = await crypto.subtle.sign('HMAC', key, enc.encode(body));
const computed = btoa(String.fromCharCode(...new Uint8Array(mac)));
return computed === signature;
}
リクエストヘッダーの x-line-signature と照合し、一致しなければ401を返す。これだけで不正リクエストを弾ける。
詰まりポイント4:ローカルでは動くのにデプロイ後に動かない
wrangler dev では問題ないのに、wrangler deploy 後にエラーが出るケース。原因の多くは Node.js固有のAPIを使っていることだ。
Cloudflare WorkersはV8ベースの実行環境でNode.jsではない。Buffer、require()、Node.jsの crypto モジュールはそのままでは使えない(互換レイヤーの対応状況はバージョンによって異なるため、公式ドキュメントで確認することを推奨する)。
よく問題になる例:
// ❌ Node.js依存のコード(Workerで動かない可能性がある)
const crypto = require('crypto');
const hmac = crypto.createHmac('sha256', secret);
hmac.update(body);
const computed = hmac.digest('base64');
前述のWeb Crypto APIを使ったコードに置き換えると解決する。
もう一つ確認すべきは wrangler.toml の compatibility_date。古い日付のままだと挙動が変わる場合がある。
name = "line-bot"
main = "src/index.js"
compatibility_date = "2026-01-01"
また、request.text() は一度しか呼べない。署名検証とJSONパースで2回呼ぼうとすると、2回目は空文字になる。最初に変数に保存して使い回すこと。
const body = await request.text(); // ← ここで保存
const sig = request.headers.get('x-line-signature') ?? '';
if (!(await verifySignature(body, sig, env.CHANNEL_SECRET))) { ... }
const { events } = JSON.parse(body); // ← 同じ変数を使い回す
詰まりポイント5:Hono・D1・KVは最初から必要か
Cloudflare関連の記事でよく登場する技術スタック:
- Hono:Cloudflare Workers向けの軽量フレームワーク
- D1:CloudflareのSQLiteベースDB
- KV:Cloudflareのキーバリューストア
LINE Botを「まず動かす」フェーズでは、これらは全て不要だ。
素のfetchとJavaScriptだけで動作する。要件が出てきた時点で追加すればよい。
| 技術 | 追加するタイミング |
|---|---|
| KV | ユーザーごとの設定値や状態を永続化したいとき |
| D1 | 会話履歴や複雑なデータをSQLで扱いたいとき |
| Hono | ルーティングが複数必要になったとき |
最初から全部入れると、どこでエラーが起きているか判断が難しくなる。最小構成で動かしてから、必要なものだけ追加する。
最小構成で動かすコード(丸ごとコピー可)
ディレクトリ構成
line-bot/
├── src/
│ └── index.js
├── .dev.vars ← .gitignore に追加
└── wrangler.toml
wrangler.toml
name = "line-bot"
main = "src/index.js"
compatibility_date = "2026-01-01"
src/index.js
export default {
async fetch(request, env) {
if (request.method !== 'POST') {
return new Response('OK', { status: 200 });
}
const body = await request.text();
const signature = request.headers.get('x-line-signature') ?? '';
if (!(await verifySignature(body, signature, env.CHANNEL_SECRET))) {
return new Response('Unauthorized', { status: 401 });
}
const { events } = JSON.parse(body);
for (const event of events) {
if (event.type === 'message' && event.message.type === 'text') {
await replyMessage(
event.replyToken,
`受け取った: ${event.message.text}`,
env.CHANNEL_ACCESS_TOKEN
);
}
}
return new Response('OK', { status: 200 });
}
};
async function verifySignature(body, signature, secret) {
const enc = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
enc.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const mac = await crypto.subtle.sign('HMAC', key, enc.encode(body));
const computed = btoa(String.fromCharCode(...new Uint8Array(mac)));
return computed === signature;
}
async function replyMessage(replyToken, text, accessToken) {
const res = 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 (!res.ok) {
const err = await res.text();
console.error('Reply API error:', err);
}
}
デプロイ手順
- Cloudflareアカウントを作成し、
npm install -g wranglerでインストール wrangler loginでブラウザ認証wrangler deployでデプロイし、発行されたURLをメモ- LINE Developersコンソールの「Messaging API設定」→「Webhook URL」に貼り付け
- シークレットを登録する:
``bash wrangler secret put CHANNEL_SECRET wrangler secret put CHANNEL_ACCESS_TOKEN ``
- LINE Developersコンソールで「検証」を実行し、成功を確認
よくあるエラーと対処
| 症状 | 考えられる原因 | 対処 |
|---|---|---|
| Webhook検証が「失敗しました」 | デプロイ前のURL・URLのコピーミス | wrangler deploy 後のURLを再確認。wrangler tail でリクエストが届いているか確認 |
| 401 Unauthorized が返り続ける | CHANNEL_SECRET が一致していない | LINE Developersコンソールで確認し wrangler secret put CHANNEL_SECRET で再設定 |
| 返信が届かない・エラーも出ない | CHANNEL_ACCESS_TOKEN の不一致 | 長期トークンを再発行し wrangler secret put CHANNEL_ACCESS_TOKEN |
Buffer is not defined | Node.js API を使っている | Web Crypto API に書き換える(本記事のコードを参照) |
| 署名検証が常に NG | body を2回 read している | request.text() は変数に保存して使い回す |
wrangler dev は動くが deploy 後に動かない | Node.js 互換レイヤーの挙動差 | Node.js 非依存の API に書き換える。wrangler tail で本番ログを確認 |
| LINEから応答がタイムアウトする | 外部API呼び出しに時間がかかっている | ctx.waitUntil() で非同期処理に切り出す(次のセクション参照) |
動いた後の「次の一手」
エコーBot(受け取ったテキストをそのまま返すBot)が動いたら、次のステップへ進める。
Claude / OpenAI API 連携でAI返信を加える
返信テキストの生成部分をClaude APIやOpenAI APIに置き換えるだけで、自然言語で回答するBotになる。ただし外部APIのレスポンス待ち時間がLINEのタイムアウト(約5秒)に引っかかる可能性があるため、ctx.waitUntil() を使って200を先に返してから処理する構成にする。
export default {
async fetch(request, env, ctx) {
// ...署名検証...
const body = await request.text();
// ...検証コード...
// 先に200を返し、返信処理をバックグラウンドで実行
ctx.waitUntil(handleAIReply(events, env));
return new Response('OK', { status: 200 });
}
};
Cloudflare Agentsの新動向(2026年6月)
2026年6月、Cloudflareは「Temporary Accounts for Agents」という機能を公開した。AIエージェントがアカウント登録なしに60分間有効な一時的なデプロイ環境を構築できるというものだ(出典:GIGAZINE 2026-06-22)。LINE BotとAIエージェントを組み合わせたアーキテクチャを検討する際の参考になる動向だ。現時点では実験的な機能とされているため、詳細は公式ドキュメントで最新状況を確認してほしい。
また、国内では「LINE Harness」と呼ばれるLINE Bot関連のOSSエコシステムがCloudflare Workers上での動作事例とともに複数の開発者ブログで取り上げられるようになっている。ツールの成熟度や保守状況は個別に確認した上で採用判断することを推奨する。
まとめ:詰まる前に知っておけばよかった5点
- Webhook URLはdeployしてから登録する——
wrangler devは外部から届かない - シークレットは
wrangler secret putで管理する——wrangler.tomlに書かない - 署名検証は最初から入れる——後から追加するのは「動いているものを壊すリスク」が増えるだけ
- Node.js APIは使わない——
Bufferやrequireは使わず Web Crypto API で書く - D1・KV・Honoは後から追加する——最小構成で動いてから拡張する
LINE Bot on Cloudflare Workers は、コストをかけずにサーバーレスBotを動かせる選択肢として引き続き有力だ。詰まりポイントを先に把握した上で取り組めば、今日中に動作確認まで辿り着ける。
【メタ情報】
タイトル案3つ
- コピペで5分のはずが3時間——Cloudflare Workers × LINE Bot 構築、詰まりポイント全部教えます【2026年版】
- なぜ動かない?Cloudflare Workers × LINE Bot 構築でつまずく5か所と正しい手順【2026年最新】
- Cloudflare WorkersでLINE Botを作るときにハマる場所——シークレット管理・署名検証・Node.jsの罠まとめ
メタディスクリプション(120字前後) Cloudflare WorkersでLINE Botを作るとき、どこで詰まるか先に教えます。Webhook URL設定・シークレット管理・署名検証・Node.js APIの罠——動かない原因を先に把握してから、最小構成コードで今日中に動作確認まで辿り着く手順を解説します。
想定読者
- Cloudflare WorkersでLINE Botを作ろうとして詰まった副業エンジニア・個人開発者
- 「コピペで5分」系の記事を試したが動かなかった人
- サーバー費用なしでLINE Botを動かしたい人
主な狙いKW + 関連KW
Cloudflare LINE Bot 作り方(メイン)Cloudflare Workers LINE BotLINE Bot Webhook 設定 できないwrangler secret 使い方LINE Bot 署名検証 JavaScriptCloudflare Workers Node.js 使えないLINE Developers Webhook 検証 失敗