---
title: "LINE Bot APIの実装手順：Webhook署名検証・reply・生成AI接続をPythonで解説"
url: "https://www.issoh.co.jp/tech/details/19339/"
published: 2026-11-05
updated: 2026-11-05
categories: ["業務システム"]
publisher: "株式会社一創"
---

# LINE Bot APIの実装手順：Webhook署名検証・reply・生成AI接続をPythonで解説

LINE Bot APIは、LINE公式アカウントに届いたメッセージをWebhookで自社サーバーへ受け取り、応答メッセージを返すMessaging APIの仕組みです。この記事では、Python SDK 3.27.0とFlaskで署名を検証し、受け取ったテキストに返信するBotを組み立てる、最小構成からの解説です。そのうえで本番運用に必要な、受信直後に200を返す非同期処理、webhookEventIdによる重複排除、応答トークンの1分制限を順に実装します。後半では、生成AIの回答を待つ間にローディング表示を出し、時間切れのときだけpushへ切り替える書き方までが解説の対象です。Messaging APIのできることや料金プランの比較は、[LINE Messaging APIのできることと2026年10月改定後の料金を整理した記事](https://www.issoh.co.jp/column/details/3302/)にまとめています。

## まとめ：Webhookは即200で返し、署名検証・重複排除・replyを非同期の処理へ

LINE Bot APIで作るチャットボットは、受信・検証・応答の3段で考えると迷いません。受信したリクエストは生のボディのまま署名を検証し、イベントをキューへ渡してすぐ200を返します。重い処理はその後ろで行い、同じwebhookEventIdは1回だけ処理します。

返信は応答トークンを使うreplyが基本で、通数に数えられません。トークンは1回限りで、受信から1分を過ぎると動作が保証されないため、生成AIを呼ぶ処理は20秒前後で打ち切ります。間に合わなかった1対1のトークに限り、pushで送り直します。

業務データを参照した回答や生成AIの出力制御が要るなら自前実装に向きます。営業時間の案内など決まった文面を返すだけなら、Webhookを作らずLINE Official Account Managerの応答メッセージで足ります。

## LINE Bot APIの構成：チャネル・Webhook・応答トークンの関係

### チャネルシークレットとアクセストークンを取得し、Webhook URLを登録する手順

LINE Bot APIという名前の独立した製品があるわけではありません。実体はLINE公式アカウントに紐づくMessaging APIチャネルで、Python向けの公式SDKもパッケージ名が `line-bot-sdk` です。開発を始める前に、LINE Developersコンソールで次の4つを済ませます。

1. LINE公式アカウントを作成し、Messaging APIを有効にしてチャネルを作る
2. ［チャネル基本設定］タブでチャネルシークレットを控える（署名検証の鍵になる）
3. ［Messaging API設定］タブでチャネルアクセストークンを発行する（APIを呼ぶときのBearerトークン）
4. 同じタブでWebhook URL（例：`https://bot.example.com/callback`）を登録し、［Webhookの利用］をオンにする

Webhook URLはHTTPSで外部から到達できる必要があります。チャネルシークレットとアクセストークンは用途が違い、前者は受信の検証、後者は送信の認証に使います。どちらもソースコードに書かず、環境変数かシークレット管理から読み込んでください。

### 応答トークンは1回限り・受信から1分以内、replyとpushの使い分け

ユーザーがメッセージを送ると、LINEプラットフォームからWebhookでイベントが届きます。応答できるイベントには `replyToken` が付いており、これを `/v2/bot/message/reply` に渡すと返信できます。[Messaging APIリファレンスの応答メッセージの項](https://developers.line.biz/ja/reference/messaging-api/#send-reply-message)が定める制約は次のとおりです（2026年11月時点）。

- 応答トークンは一度だけ使える
- Webhookを受信してから1分以内に使う必要があり、1分を超えた場合の動作は保証されない
- 再送されたWebhookのトークンも受信から1分以内なら使えるが、元のトークンを使用済みの場合と、イベント発生から20分が過ぎた場合は使えない
- レート制限は2,000リクエスト/秒、1回に送れるメッセージは5件まで

リファレンスは、この時間制限は予告なく変わることがあり、制限に依存した実装をしないよう求めています。[Messaging APIの料金](https://developers.line.biz/ja/docs/messaging-api/pricing/)では応答メッセージは通数に数えないため、ユーザーの発言への返事はreplyで返すのが原則です。こちらから先に送る通知はpushの役割で、その実装は[pushとmulticastの送信とリトライキーを解説した記事](https://www.issoh.co.jp/tech/details/19335/)で扱っています。

## Python SDK 3.27.0とFlaskでオウム返しBotを動かす最小構成の手順

### line-bot-sdkの導入から署名検証までを行う最小のFlaskコード

[PyPIのline-bot-sdk](https://pypi.org/project/line-bot-sdk/)は2026年10月27日公開の3.27.0が最新で、Python 3.10以上が必要です。[公式リポジトリのREADME](https://github.com/line/line-bot-sdk-python)にあるFlaskの例を、受け取った文字列をそのまま返す形で書くと次のようになります。

```
pip install line-bot-sdk flask

# app.py
import os
from flask import Flask, abort, request
from linebot.v3 import WebhookHandler
from linebot.v3.exceptions import InvalidSignatureError
from linebot.v3.messaging import (
    ApiClient, Configuration, MessagingApi, ReplyMessageRequest, TextMessage,
)
from linebot.v3.webhooks import MessageEvent, TextMessageContent

app = Flask(__name__)
config = Configuration(access_token=os.environ["LINE_CHANNEL_ACCESS_TOKEN"])
handler = WebhookHandler(os.environ["LINE_CHANNEL_SECRET"])


@app.post("/callback")
def callback():
    signature = request.headers.get("X-Line-Signature", "")
    body = request.get_data(as_text=True)  # 加工せずに受け取る
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)
    return "OK"


@handler.add(MessageEvent, message=TextMessageContent)
def echo(event):
    with ApiClient(config) as client:
        MessagingApi(client).reply_message(ReplyMessageRequest(
            reply_token=event.reply_token,
            messages=[TextMessage(text=event.message.text)],
        ))
```

`flask --app app run --port 8000` で起動し、トンネルツールなどでHTTPSの公開URLを用意してコンソールに登録すれば、自分のLINEから送った文がそのまま返ってきます。`WebhookHandler` は署名を検証し、イベントの種類ごとに登録した関数を呼び分けます。

### opensslで署名を作り、curlで/callbackへ送る受信テストの手順

署名検証は、[Webhookの署名を検証する手順](https://developers.line.biz/ja/docs/messaging-api/verify-webhook-signature/)のとおり、リクエストボディの文字列を入力、チャネルシークレットを鍵にしてHMAC-SHA256で計算した値を `x-line-signature` ヘッダと比べます。同じ計算をopensslで行えば、LINEに繋がなくても手元のサーバーをテストできます。

```
BODY='{"destination":"Uxxxxxxxx","events":[]}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$LINE_CHANNEL_SECRET" -binary | openssl base64)
curl -i -X POST http://localhost:8000/callback \
  -H 'Content-Type: application/json' \
  -H "X-Line-Signature: $SIG" \
  -d "$BODY"
```

200が返れば検証は通っています。署名を1文字変えると400になります。ガイドは「署名の検証を行う前にデータの変更をしないでください」としており、JSONとして読み込んでから文字列に戻したボディや、整形し直したボディでは一致しません。Flaskなら `request.get_json()` ではなく `request.get_data(as_text=True)` の値を検証に渡します。検証に失敗したリクエストは、イベントの内容に応じた処理をせずエラーとして終えるのがガイドの指示です。

## 本番のWebhook受信で必要な非同期処理・重複排除・再送への備え

### 受信直後に200を返し、イベント処理をキューへ渡す非同期化の書き方

最小構成は、返信を送り終えるまで200を返しません。[Webhookの受信のドキュメント](https://developers.line.biz/ja/docs/messaging-api/receiving-messages/)は、後続のリクエストが待たされないようイベントを非同期で処理するよう推奨しています。200番台を返さなかったWebhookは、再送を有効にしていれば送り直され、受信に長く失敗し続けると送信を止められる可能性もあります。

そこで、受信ハンドラーは署名検証とキュー投入だけにします。SDKの `WebhookParser` は検証してイベントの一覧を返すだけなので、この形に向いています。

```
from concurrent.futures import ThreadPoolExecutor
from linebot.v3 import WebhookParser

parser = WebhookParser(os.environ["LINE_CHANNEL_SECRET"])
pool = ThreadPoolExecutor(max_workers=8)


@app.post("/callback")
def callback():
    body = request.get_data(as_text=True)
    try:
        events = parser.parse(body, request.headers.get("X-Line-Signature", ""))
    except InvalidSignatureError:
        abort(400)
    for event in events:
        pool.submit(handle, event)  # 返信や生成AIの呼び出しは後ろで行う
    return "OK"
```

スレッドプールはプロセスが落ちると処理中のイベントを失います。取りこぼしが許されない業務Botなら、Amazon SQSやCloud Tasksのような外部のキューに積み、別のワーカーで処理する構成にします。

### webhookEventIdで重複を捨て、isRedeliveryのイベントを扱う条件

ドキュメントによると、ネットワーク経路の問題などで同じイベントが重複して届くことがあります。Webhookの再送は既定で無効ですが、有効にすると再送分も届き、発生順と到着順が必ずしも一致するわけではありません。どちらも、イベントごとの `webhookEventId`（ULID形式の一意なID）で見分けます。

```
import redis

r = redis.Redis.from_url(os.environ["REDIS_URL"])


def handle(event):
    # 初めて見たIDのときだけ True。24時間で自動的に消える
    if not r.set(f"line:event:{event.webhook_event_id}", 1, nx=True, ex=86400):
        return
    ...
```

再送されたイベントは `deliveryContext.isRedelivery` が true になり、それ以外は元と同じ内容です。再送分の応答トークンも、受信から1分以内で元のトークンが未使用なら使えます。ただし再送は確実な配信を保証しないので、取りこぼしを防ぐ手段ではなく、一時的な障害で失ったイベントを拾うための補助という位置づけです。受信側の冪等性の考え方は、[Webhookの署名検証と冪等性をNode.js・Pythonで実装した記事](https://www.issoh.co.jp/tech/details/1378/)で詳しく書いています。

### mode=standbyのイベントとグループトークで応答を止める判定

イベントの共通プロパティ `mode` が `standby` のときは、別のモジュールがユーザーとやり取りしている状態で、応答トークンが含まれません。リファレンスもこの状態ではメッセージ送信を控えるよう求めているため、`active` 以外は処理を打ち切ります。

送信元は `source.type` で `user`・`group`・`room` を見分けます。replyはグループや複数人トークのイベントにも返せますが、全発言に生成AIが答えると会話の妨げになるため注意が必要です。グループでは、Botへのメンションを含む発言だけに答えるといった条件を先に決めておきます。

## 生成AIを繋ぐLINEチャットボットでローディング表示と1分制限を両立

### ローディングアニメーションを30秒表示し、回答待ちを利用者に伝える

生成AIの回答には数秒から十数秒かかり、その間トーク画面は無反応に見えます。[ローディングのアニメーションを表示するAPI](https://developers.line.biz/ja/reference/messaging-api/#display-a-loading-indicator)を使うと、入力中を示す表示を出せます。エンドポイントは `POST /v2/bot/chat/loading/start` で、仕様の要点は次のとおりです。

| 項目             | 値               |
| -------------- | --------------- |
| loadingSeconds | 5〜60の5秒刻み（既定20） |
| 応答             | 202と空のJSON      |
| レート制限          | 100リクエスト/秒      |
| 表示できる相手        | 1対1のトークのみ       |

表示は指定秒数が過ぎるか、公式アカウントからメッセージが届くと消えます。[ドキュメント](https://developers.line.biz/ja/docs/messaging-api/use-loading-indicator/)によると、トーク画面を開いていないユーザーには表示されず、後から開いても出ません。202が返っても表示されたとは限らないため、表示の成否で処理を分けない作りにします。

### Claude APIを20秒で打ち切り、replyが失効したときだけpushで送る実装

応答トークンの1分に余裕を持たせるため、生成AIの呼び出しには20秒のタイムアウトを付けます。SDK側の自動リトライも止めておかないと、再試行の分だけ待ち時間が延びます。下の例は[Claude Messages API](https://platform.claude.com/docs/en/api/messages)を使った場合で、他社のAPIでも構造は同じです。

```
import uuid
import anthropic
from linebot.v3.messaging import (
    ApiException, PushMessageRequest, ShowLoadingAnimationRequest,
)

llm = anthropic.Anthropic(timeout=20.0, max_retries=0)


def ask_llm(text):
    try:
        res = llm.messages.create(
            model="claude-sonnet-5-5",
            max_tokens=800,
            system="店舗の問い合わせ窓口として、営業時間と予約方法だけに答える。分からないことは電話番号を案内する。",
            messages=[{"role": "user", "content": text}],
        )
        return res.content[0].text
    except anthropic.APITimeoutError:
        return "回答の準備に時間がかかっています。少し時間をおいて、もう一度送ってください。"


def answer(api, event):
    if event.source.type == "user":  # ローディング表示は1対1だけ
        api.show_loading_animation(ShowLoadingAnimationRequest(
            chat_id=event.source.user_id, loading_seconds=30))
    text = ask_llm(event.message.text)[:5000]  # テキストは1件5,000字まで
    try:
        api.reply_message(ReplyMessageRequest(
            reply_token=event.reply_token, messages=[TextMessage(text=text)]))
    except ApiException as e:
        if e.status != 400 or event.source.type != "user":
            raise
        # Invalid reply token（期限切れ・使用済み）。pushは通数に数える
        api.push_message(
            PushMessageRequest(to=event.source.user_id, messages=[TextMessage(text=text)]),
            x_line_retry_key=str(uuid.uuid4()))
```

リファレンスでは、期限切れや使用済みの応答トークンを使うと400で「Invalid reply token」が返ります。replyは[リトライキー](https://developers.line.biz/ja/docs/messaging-api/retrying-api-request/)の対象外なので、500が返った返信を同じ内容で送り直すと二重に届くおそれがあるため、注意が必要です。push側にはリトライキーを付けられるため、再送するならpushの経路だけにします。

### 回答範囲をsystemで絞り、会話履歴はuserIdごとに直近だけ渡す設計

LINEの公式アカウントは誰でも友だち追加でき、想定外の質問や指示の書き換えを狙った入力も届きます。systemプロンプトで答える範囲を業務の話題に限り、範囲外は問い合わせ窓口へ案内する文面をあらかじめ決めておく設計です。生成AIに渡す前に、個人情報を含みうる入力をログへそのまま残さない設計も要ります。

会話を続けたい場合は、`source.userId` をキーに履歴を保存し、直近の数往復だけを `messages` に入れます。全履歴を渡すと入力トークンが会話のたびに増え、応答時間も延びるため、1分の枠を圧迫する原因になります。この点も履歴を絞る理由です。社内文書を根拠に答えさせるRAGの構成や方式の比較は、[生成AIとRAGで作るAIチャットボットの方式選定と実装手順の記事](https://www.issoh.co.jp/tech/details/15343/)が参考になります。

## LINE Bot APIを自前で実装する条件と、管理画面の応答設定で足りる場面

### 業務データの参照や生成AIの回答制御が要るなら自前実装を選ぶ判断

予約状況や注文履歴を引いて答える、生成AIの回答範囲を業務ルールで縛る、会話を基幹システムの記録に残す。こうした要件があるなら、Webhookを自前で受けて実装する価値があります。応答の中身がすべて自社のデータとロジックで決まり、既製の応答設定では表現できないからです。予約台帳と組み合わせた構成は[LIFFとMessaging APIで作るLINE予約システムの記事](https://www.issoh.co.jp/tech/details/18647/)、会員IDとの紐づけは[LINEログインのIDトークン検証と既存会員IDの連携を解説した記事](https://www.issoh.co.jp/tech/details/19337/)が具体例になります。

このとき作り込む対象は、返信のAPI呼び出しよりもその周辺です。署名検証、キューによる非同期化、webhookEventIdの重複排除、standbyの判定、生成AIのタイムアウトとpushへの切り替え、入力ログの扱い。この6つが揃ってはじめて本番に出せます。生成AIの回答品質の評価まで含めて任せたい場合は、[一創のAIチャットボット開発](https://www.issoh.co.jp/service/ai/chatbot/)でLINE連携を含めた設計から相談できます。

### 決まった文面を返すだけなら管理画面の応答メッセージで十分な場面

反対に、営業時間・アクセス・よくある質問への定型の返事だけなら、Botを自作しません。LINE Official Account Managerの応答メッセージ機能で、キーワードに応じた返信を画面から設定できます。サーバーの監視も署名検証もトークン管理も要りません。

判断の目安は、返信の文面が問い合わせごとに変わるかどうかです。全員に同じ答えを返すなら管理画面、相手の予約や注文によって答えが変わるならAPI。生成AIで自由文に答えさせる場合も、誤回答が業務上の損失に直結する窓口では、選択肢ボタンで用件を絞ってから答える構成にとどめるほうが安全です。

## よくある質問

LINE Bot APIでチャットボットを実装するときに、つまずきやすい質問をまとめました。

### LINE Bot APIとMessaging APIは同じものですか？

同じものを指しています。LINEのBotを作るためのAPIの正式な名称はMessaging APIで、LINE Developersのドキュメントやリファレンスで使われている名前もMessaging APIです。一方で公式SDKのパッケージ名は、Python版が `line-bot-sdk` のように「line-bot」を含むため、開発者の間ではLINE Bot APIとも呼ばれます。調べるときは「Messaging API」で検索すると公式情報に早く当たります。

### LINE Botの応答メッセージは無料ですか？通数にカウントされますか？

応答トークンを使って返すreplyは、メッセージ通数に数えられません。月の無料メッセージ通数を使い切っていても返信できるため、ユーザーの発言への返事はreplyで返すのが基本です。pushで送った分は通数に数えるので、生成AIの回答が間に合わずpushへ切り替えた場合はその分だけ通数を消費します。プラン別の無料通数と追加料金は3302の記事で確認してください。

### 署名検証でInvalidSignatureErrorになる原因は何ですか？

多いのは3つです。チャネルシークレットではなくアクセストークンを鍵にしている、別チャネルのシークレットを使っている、検証前にボディを加工している。フレームワークがJSONを自動で読み込む設定だと、元の文字列が失われて署名が合いません。生のボディを文字列で受け取り、そのまま検証関数に渡しているかをまず確認します。

### 応答トークンの期限が切れたらどうなりますか？

replyのリクエストが400で失敗し、エラーメッセージに「Invalid reply token」が入ります。使用済みのトークンを使った場合も同じです。その返信は届かないため、1対1のトークならユーザーIDを宛先にpushで送り直せます。グループではpushで割り込むと会話の流れを乱しやすいので、返信を諦めるか、処理時間そのものを短くする方向で直します。

### グループトークでもLINE Botは使えますか？

使えます。replyはユーザー、グループトーク、複数人トークのどのイベントにも返信できます。ただし、ローディングのアニメーションは1対1のトークにしか表示できず、グループトークや複数人トークは表示先に指定できません。グループのイベントでは送信元に `groupId` が入り、発言者の `userId` は含まれないことがあるので、ユーザー単位の処理は値の有無を確かめてから行います。

## 関連記事

- [LINE Messaging APIとは？できること・2026年10月改定後の料金と実装手順](https://www.issoh.co.jp/column/details/3302/)：Messaging API全体の機能とプランの選び方です。
- [LINE Messaging APIのプッシュメッセージ送信：push・multicastの実装とリトライキー](https://www.issoh.co.jp/tech/details/19335/)：Botからこちらが先に送る通知の実装です。
- [AIチャットボットの作り方｜生成AI＋RAG・ノーコード・受託開発の方式選定と実装手順](https://www.issoh.co.jp/tech/details/15343/)：LINEに載せる回答エンジンの選び方です。
- [Webhookの作り方と実装方法｜受信側の署名検証・冪等性と送信側の再送をNode.js・Pythonで解説](https://www.issoh.co.jp/tech/details/1378/)：Webhook受信の一般的な作法です。
- [LINE予約システムを自作する：LIFFとMessaging APIの構成・コード・料金【2026年10月】](https://www.issoh.co.jp/tech/details/18647/)：Botと予約台帳を組み合わせた構成例です。

---

出典: [LINE Bot APIの実装手順：Webhook署名検証・reply・生成AI接続をPythonで解説](<https://www.issoh.co.jp/tech/details/19339/>)（株式会社一創）
