Claude CodeでLINE Botの作り方 — Webhook受信からSDK応答まで
Claude Codeを使い、LINE Messaging APIのWebhook受信からSDKでの応答実装まで、LINE Botを一気通貫で作る手順です。
LINE Botのしくみと今回作るもの
Claude CodeでLINE Botを作るときは、まずLINE公式アカウントでMessaging APIを有効にします。次に、Claude Codeに書かせたWebhookサーバーをHTTPSで公開し、そのURLを登録します。このLINE連携は、Claude Codeでbotのコードを書き、LINEのMessaging APIとつなぐ方法を指します。
LINE Messaging APIのbotは、ユーザーがLINE公式アカウントにメッセージを送るところから動き出します。通信はすべてHTTPS上のJSONで、流れは次の4段階です。
メッセージが往復する流れ
- 1
ユーザーが送信する
LINEアプリから公式アカウントにテキストを送ります。友だち追加やブロック解除も、イベントとして届きます。
- 2
LINEがWebhookをPOSTする
LINEプラットフォームが、登録済みのWebhook URL(botサーバー)へイベントのJSONをHTTP POSTします。
- 3
botサーバーが署名を検証する
x-line-signatureヘッダーの署名を検証してから、イベントの中身を読みます。 - 4
reply APIで応答する
イベントに付いた
replyTokenを指定して、返信メッセージを送ります。
本記事では、この流れを実装するNode.js製のWebhookサーバーを、Claude Codeにコードを書かせながら組み立てます。完成品は、送られたテキストをそのまま返すオウム返しbotです。返信ロジックだけ差し替えれば、実用botに発展させられます。
手順の途中で、署名検証の失敗パターンを実際のサーバーで試した結果も載せます。
前提条件
- Node.js 22以上 —
@line/bot-sdkのREADMEが「Node.js 22 or higher」と書いており、v11.1.0以降のenginesも>=22です - Claude Codeのセットアップ済み環境
- ローカル開発用のHTTPSトンネル(
ngrokなど) — Webhook URLはHTTPS必須で、SDKのガイドは開発用途ならngrokで十分だと説明しています - LINE公式アカウントとMessaging APIチャンネル(作成手順は次章)
Node.jsのバージョンは node -v で確認できます。@line/bot-sdk は、v11.0.0までは engines が >=20 で、v11.1.0(2026年7月1日)以降は >=22 です。READMEの「22以上」と揃っているのは、v11.1.0以降です。Node.js 20系で使うなら、npm install @line/bot-sdk@11.0.0 のようにv11.0.0を指定します。
Node.js v20.18.0の環境では、v11.0.0のインストールと後述するWebhookの疎通が動きました。
Claude Code自体の動作に必要なNode.jsバージョンは、インストール方法によって変わります。そちらはClaude CodeにNode.jsは必要かの話で、ここで問題になるのは @line/bot-sdk を動かすランタイム側のバージョンです。
手順1: LINE Official AccountとMessaging APIチャンネルを作る
Messaging APIを使うには、まずチャンネルが必要です。チャンネルは、LINE Developers Consoleではなく、次の2段階で作ります。
- LINE Official Accountを作る: Business IDに登録し、表示される申込フォームに必要事項を入力します。作成済みのアカウントは、LINE Official Account Managerで確認できます。
- Messaging APIを有効化する: LINE Official Account Managerで有効化すると、そのアカウントに紐づくチャンネルが作られます。
有効化のあとは、LINE Official Account Managerのログインに使ったアカウントでLINE Developers Consoleにログインします。そのアカウントでConsoleを使うのが初めてなら、名前とメールアドレスを入れる開発者登録の画面が出ます。次にプロバイダーを選びますが、既存のプロバイダーを選ぶには、そのプロバイダーでAdminロールを持っている必要があります。最後に、プロバイダーの下にチャンネルができていることを確かめます。
チャンネルができたら、Developers Consoleでチャンネルを開き、チャンネルシークレットとチャンネルアクセストークンを控えます。この2つが、この後のコードで使う認証情報です。
手順2: プロジェクトをClaude Codeで初期化する
作業ディレクトリを作り、Claude Codeに「ExpressとLINEのNode.js SDKでLINE Botのプロジェクトを初期化して」と指示すれば、インストールまで一括で進みます。手で打つ場合は次のとおりです。
mkdir claude-line-bot && cd claude-line-bot
npm init -y
npm install express @line/bot-sdk dotenv
npm install -D typescript @types/node @types/express tsx
mkdir src次の手順3のコードは dotenv/config を読み込むので、dotenv もインストール対象に入れています。.env にチャンネルシークレットとアクセストークンを保存します。Claude Codeに .gitignore へ .env を追記させておくと、うっかりコミットする事故を防げます。
CHANNEL_SECRET=手順1で控えたチャンネルシークレット
CHANNEL_ACCESS_TOKEN=手順1で控えたチャンネルアクセストークン
PORT=3000手順3: Webhookサーバーの実装をClaude Codeに書かせる
@line/bot-sdk は、署名検証とWebhookイベントのパースをまとめて行う middleware() を提供します。Claude Codeに「middleware() を使ったExpressのWebhookエンドポイントを src/server.ts に書いて」と依頼すると、次のような実装が得られます。
import "dotenv/config";
import express from "express";
import { middleware, LineBotClient } from "@line/bot-sdk";
const config = {
channelSecret: process.env.CHANNEL_SECRET!,
};
const client = LineBotClient.fromChannelAccessToken({
channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN!,
});
const app = express();
app.post("/webhook", middleware(config), (req, res) => {
Promise.all(req.body.events.map(handleEvent))
.then((result) => res.json(result))
.catch((err) => {
console.error(err);
res.status(500).end();
});
});
function handleEvent(event: any) {
if (event.type !== "message" || event.message.type !== "text") {
return Promise.resolve(null);
}
return client.replyMessage({
replyToken: event.replyToken,
messages: [{ type: "text", text: event.message.text }],
});
}
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`listening on ${port}`);
});ここで押さえる点は2つです。SDKのガイドは、どちらも「やってはいけない例」として載せています。
middleware()は/webhookルート専用に登録する。全ルートにapp.use(middleware(config))を適用すると、X-Line-Signatureヘッダーを持たない通常のリクエストで例外が発生します- 他のbody-parserを
middleware()より先に登録しないこと。署名検証にはリクエストの生のボディが必要で、別のparserが先にボディを消費すると検証に失敗します
Claude Codeにこの2点を制約として伝えておくと、生成されるコードの順序ミスを避けやすくなります。違反するとどう壊れるかは、次章の実測が示します。
手順4: ローカルで動かしてWebhook URLを登録する
Webhook URLはHTTPSである必要があります。ローカルでは ngrok でトンネルを張り、一時的なHTTPS URLを発行します。
npx tsx src/server.ts別ターミナルで次を実行します。
ngrok http 3000発行された https://xxxx.ngrok-free.app/webhook を、Developers ConsoleのMessaging APIタブにあるWebhook URL欄に登録します。続けて「Use webhook」を有効にします。登録したら、同じ欄の「Verify」ボタンで疎通を確かめます。
Verifyは、イベントを含まない {"destination": "...", "events": []} のPOSTをbotサーバーに送り、ステータスコード200が返るかを見ます。上のコードなら、署名を正しく付けた空のイベント配列に対して middleware() が通し、200を返します。
LINEアプリから公式アカウントにテキストを送り、同じ文言が返ってくれば疎通は成功です。
本番で効く3つの仕様
LINE公式のWebhookガイドには、オウム返しの先で効いてくる記述が3つあります。
- イベントは非同期で処理することが推奨されています。同期処理が長引くと、後続のリクエストが待たされるためです。返信の生成に時間がかかるbotでは、先に200を返してから処理する構成が向いています
- 同じイベントが2回以上届くことがあります。ネットワーク経路の問題などが理由で、重複の判定にはイベントの
webhookEventIdを使います。再配信(redelivery)を有効にすると、イベントの到着順が実際の発生順と入れ替わることもあり、順序が問題ならtimestampで判断します - 再配信は初期状態で無効です。Messaging APIタブの「Use webhook」と「Webhook redelivery」をどちらも有効にすると働き、botサーバーが2xxを返さなかったときに同じイベントが再送されます
署名検証の失敗パターンを実際に動かして確かめる
署名検証では、LINEプラットフォームとbotサーバーが、同じチャンネルシークレットでHMAC-SHA256の署名を計算します。その結果を x-line-signature ヘッダーと突き合わせます。リクエストボディを一文字でも加工してから検証すると、一致しなくなります。
この挙動を、@line/bot-sdk v11.0.0・Express 5.2.1・Node.js v20.18.0の環境で試しました。ダミーのチャンネルシークレットでWebhookサーバーを起動し、同じ端末から fetch でPOSTした応答が次の表です。LINEには接続していません。
| 条件 | /webhook の応答 | 同じサーバーの GET /health |
|---|---|---|
/webhook だけに middleware()(推奨構成) | /webhook の応答正しい署名 → 200、誤った署名・署名なし → 401、壊れたJSON → 400 | 同じサーバーの GET /health200 |
app.use(middleware(config)) で全ルートに適用 | /webhook の応答誤った署名・署名なし → 401、壊れたJSON → 400 | 同じサーバーの GET /health401(SignatureValidationFailed) |
express.json() を middleware() の前に登録 | /webhook の応答正しい署名でも401 | 同じサーバーの GET /health200 |
全ルートに適用すると、署名ヘッダーを持たないヘルスチェックまで401になります。死活監視が GET /health を叩く構成では、監視側が異常と判断する原因になります。
express.json() を先に置いた構成は、さらに厄介です。署名が正しくても401になり、エラーの文面からは署名の不一致と区別がつきません。「署名が合わない」と見えたら、シークレットの打ち間違いより先に、ミドルウェアの登録順を疑います。ボディが壊れたJSONのとき、この構成では middleware() ではなく express.json() が SyntaxError を投げ、400ではなく500になりました。
手順5: エラーハンドリングを仕上げる
middleware() が投げるエラーは2種類です。署名が無い、または一致しない場合の SignatureValidationFailed と、リクエストボディがJSONとしてパースできない場合の JSONParseError です。Expressのエラーハンドリングミドルウェアで、この2つを区別して処理します。上の実測で401と400を返したのは、このコードです。
import { JSONParseError, SignatureValidationFailed } from "@line/bot-sdk";
app.use(
(err: unknown, req: express.Request, res: express.Response, next: express.NextFunction) => {
if (err instanceof SignatureValidationFailed) {
res.status(401).send(err.signature);
return;
}
if (err instanceof JSONParseError) {
res.status(400).send(err.raw);
return;
}
next(err);
}
);app.listen() より前に、/webhook ルートの後ろへ置きます。err.signature や err.raw をそのまま返す書き方はSDKガイドの例どおりですが、本番では署名やボディをレスポンスに出さず、ログに残すだけにする書き方もあります。
送信側(client.replyMessage() / client.pushMessage())のエラーは HTTPFetchError として投げられます。2xx以外のHTTPステータスが返ったときのエラーで、status・statusText・headers・body を持ちます。Claude Codeに「LINE APIのエラーレスポンスをログに残すcatch節を追加して」と頼めば、このプロパティを使った出力を足してくれます。
reply messageとpush messageの違いと送信数の数え方
bot開発でまず使う送信方法は、reply messageとpush messageの2つです。
reply と push の使い分け
reply message
ユーザーのメッセージやアクションへの返信に使います。Webhookイベントに付く replyToken を指定します。1リクエストで最大5件のメッセージオブジェクトを送れます。
push message
ユーザーの操作を起点にしない通知や、バッチ処理の結果通知に使います。送信先は、ユーザー・グループチャット・複数人チャットです。replyと同じく、1リクエストで最大5件のメッセージオブジェクトを送れます。
オウム返しのようにユーザー発話への即時応答だけなら、replyで足ります。
注意したいのは、送信数の数え方です。LINEの送信数は、メッセージオブジェクトの数ではなく、送った相手の人数で数えます。たとえば、4つのメッセージオブジェクトを1回のpushで5人のチャットルームに送ると、送信数は5です。ブロックされているユーザーや、存在しないユーザーIDへの送信はカウントされません。
月ごとの送信数には上限があり、超えると配信に失敗します。送信前に残り枠を確かめたいときは、Messaging APIに「今月の送信上限を取得する」エンドポイントと、「今月の送信数を取得する」エンドポイントがあります。
返信の準備に時間がかかるときは、ローディングアニメーション(POST /v2/bot/chat/loading/start)でユーザーに待ち状態を見せられます。
よくあるつまずき
SignatureValidationFailed が発生する
middleware() より前に express.json() などのbody-parserを挟んでいないか確認します。署名検証は生のリクエストボディを必要とするため、先にパースされると、署名が正しくても401になります(上の実測を参照)。
ヘルスチェックが401になる
app.use(middleware(config)) で全ルートに適用していないか確認します。/webhook にだけ付けます。
Webhookからの応答が来ない
Webhook URLがHTTPSになっているか、Developers Consoleで「Use webhook」が有効になっているかを確認します。LINEは、Webhookの受信に長く失敗し続けるbotサーバーへの送信を止めることがあります。Verifyボタンで200が返るかを見ます。受信に失敗した原因の調べ方は、LINE公式のWebhookエラー原因と統計の確認ページにまとまっています。トンネルを張り直してURLが変わった場合は、Webhook URLを登録し直します。
再配信されたイベントに返信しようとして失敗する
再配信されたイベントは、deliveryContext.isRedelivery 以外は元のイベントと同じ内容で、replyToken の値も変わりません。再配信のreply tokenは、特定の場合を除いて使えるとされています。返信に失敗したら、HTTPFetchError の body にあるエラーレスポンスを読みます。
署名検証を自前実装しようとして詰まる
validateSignature() を手動で呼ぶ構成は、Firebase Cloud Functionsのようにボディが事前にパースされる環境向けの方法です。ボディが文字列かBufferなら、middlewareはそのまま使えます。パース済みのオブジェクトだと働かないので、生のボディで手動検証します。通常のExpress構成では middleware() に任せるほうがシンプルです。
まとめ
LINE Botは、チャンネル作成(LINE Official Account Manager経由)、署名検証つきのWebhook受信、reply APIでの応答という3段階で作れます。落とし穴は、middleware() を /webhook だけに、body-parserより前に置く、という順序の2点に集約されます。
Slack向けに同じ発想でbotを作る場合は、Anthropic Agent SDKでSlack常駐botを作る記事も参考になります。Telegramならwebhookの共有シークレットとトークンの権限設計が要点で、Claude CodeでTelegram Botを作る手順にまとめています。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- developers.line.biz/en/docs/messaging-api/overview
- developers.line.biz/en/docs/messaging-api/receiving-messages
- developers.line.biz/en/docs/messaging-api/sending-messages
- developers.line.biz/en/docs/messaging-api/getting-started
- line.github.io/line-bot-sdk-nodejs/guide/webhook.html
- line.github.io/line-bot-sdk-nodejs/guide/client.html
- github.com/line/line-bot-sdk-nodejs/blob/master/README.md
- developers.line.biz/en/docs/messaging-api/verify-webhook-url
- developers.line.biz/en/docs/messaging-api/verify-webhook-signature