Claude Media
Claude CodeでLINE Botの作り方 — Webhook受信からSDK応答まで

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. 1

    ユーザーが送信する

    LINEアプリから公式アカウントにテキストを送ります。友だち追加やブロック解除も、イベントとして届きます。

  2. 2

    LINEがWebhookをPOSTする

    LINEプラットフォームが、登録済みのWebhook URL(botサーバー)へイベントのJSONをHTTP POSTします。

  3. 3

    botサーバーが署名を検証する

    x-line-signature ヘッダーの署名を検証してから、イベントの中身を読みます。

  4. 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段階で作ります。

  1. LINE Official Accountを作る: Business IDに登録し、表示される申込フォームに必要事項を入力します。作成済みのアカウントは、LINE Official Account Managerで確認できます。
  2. 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を作る手順にまとめています。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. developers.line.biz/en/docs/messaging-api/overview
  2. developers.line.biz/en/docs/messaging-api/receiving-messages
  3. developers.line.biz/en/docs/messaging-api/sending-messages
  4. developers.line.biz/en/docs/messaging-api/getting-started
  5. line.github.io/line-bot-sdk-nodejs/guide/webhook.html
  6. line.github.io/line-bot-sdk-nodejs/guide/client.html
  7. github.com/line/line-bot-sdk-nodejs/blob/master/README.md
  8. developers.line.biz/en/docs/messaging-api/verify-webhook-url
  9. developers.line.biz/en/docs/messaging-api/verify-webhook-signature
この記事を共有:XはてブLinkedIn