LINE Botをローカルで開発する。WebhookのURLが毎回変わる問題を固定URLで解決する

LINE Botをローカルで開発する。WebhookのURLが毎回変わる問題を固定URLで解決する

こんにちは!

LINE Botを作ろうとして最初にぶつかる壁は、コードではなくWebhookです。

LINEのMessaging APIは、ユーザーがBotに送ったメッセージを、LINEのサーバーからこちらのサーバーへHTTPSのPOSTで通知してきます。

この受け口(Webhook URL)にはインターネットから届くHTTPSのURLしか指定できず、開発中の http://localhost:5000 をそのまま書くことはできません。

トンネルツールで一時URLを発行して回避する方法が定番ですが、今度は別の問題が待っています。トンネルの方式によっては、起動のたびにランダムなURLが発行されます。その場合、開発を再開するたびにLINE Developersコンソールを開いてWebhook URLを書き換えることになります。コードを直すたび、翌日再開するたび、まずURLの貼り直しから。これが地味に堪えます。

結論から言うと、

ローカルPCの開発サーバーに「変わらない公開URL」を1本付けてしまえば、Webhook URLの設定は最初の1回だけで終わります。

この記事では、LINE公式アカウントの開設からMessaging APIの設定、PythonでのWebhook受信サーバーの実装、固定URLでの公開、スマホの実機でBotが返事するところまでを、最初から最後まで書きます。

手順・コマンド・画面はすべて筆者が実際に動かして確認したものです
(確認日は2026年8月11日。LINE側の画面は変わることがあります)。

なお、Botの中身は受け取ったメッセージをそのまま返す最小構成にしてあります。この記事のゴールは「LINEからのWebhookがローカルのPythonに届き、返事がスマホに出る」までの経路を作りきることです。

図1 LINE Botをローカルで開発する構成。Webhook URLの設定は最初の1回だけになる
図1 LINE Botをローカルで開発する構成。Webhook URLの設定は最初の1回だけになる

手順1: LINE公式アカウントを作る(2026年8月の現行手順)

まず知っておきたいのは、

Messaging APIチャネルはLINE Developersコンソールから直接作れなくなった

現在この画面には
「LINE公式アカウントを作成した後、LINE Official Account Manager上でMessaging APIの利用を有効にしてください」
と案内が出ます。

現行の流れはこうなります。

  1. LINEビジネスID(LINEヤフーBusiness ID)を作る。メールアドレスだけで作成でき、LINEアプリのアカウントは不要です
  2. LINE Developersコンソールに開発者登録し、プロバイダーを作る
  3. LINE公式アカウントを作る。作成時にビジネスマネージャー組織との接続を求められるので、組織がなければその場で作成します(組織名は空欄ならアカウント名と同じになります)
  4. LINE Official Account ManagerでMessaging APIを有効にする

プロバイダーは、このBotを提供する個人・企業・団体を表す単位です。LINEログインなどと組み合わせた場合には同意画面にも表示されるため、会社名やサービス名など、利用者が提供元を判断できる名前にしておくのが無難です
(筆者は「Qualiteg」で作りました)。

公式アカウントの作成フォームでは、アカウント名(友だちリストとトーク画面に表示されるBotの名前)、会社・事業者名、業種などを入力します。ここでひとつ注意があります。公式アカウントの新規作成には、電話番号認証(SMSまたは通話)が必要です。画面の案内によれば、認証に使った電話番号が友だちに公開されることはありません。

アカウントの種別は「未認証アカウント」のままで大丈夫です。認証済アカウント(緑色の認証バッジ)はLINE内の検索対象になるなどのメリットがありますが、開発用のBotには必要ありません。

手順2: Messaging APIを有効にして、鍵を3つ手に入れる

公式アカウントができたら、LINE Official Account Manager(管理画面)を開き、設定の「Messaging API」から「Messaging APIを利用する」を押します。ここでプロバイダーを選びますが、一度ひもづけたプロバイダーはあとから変更できません。手順1で作ったプロバイダーを選んでください。

有効になると、この画面にChannel IDとChannel secretが表示されます。

OAMのMessaging API画面
OAMのMessaging API画面

Botの開発に必要な「鍵」は3つあります。

鍵何に使うかどこにあるか
Channel IDチャネルの識別子Official Account Manager/Developersコンソール
Channel secretWebhookの署名検証(なりすまし防止)同上
チャネルアクセストークン(長期)Botからの返信APIの認証Developersコンソールの「Messaging API設定」タブで「発行」

チャネルアクセストークンだけはDevelopersコンソール側での発行操作が必要です。発行したトークンとChannel secretは、コードに直書きせず環境変数で渡します(次の手順で使います)。

手順3: Webhookを受ける最小のBotを書く

サーバーはPythonで書きます。公式SDK(line-bot-sdk)とFlaskを入れます。

pip install line-bot-sdk flask

筆者環境ではline-bot-sdk 3.25.0とflask 3.1.3が入りました(Python 3.11)。

app.py(全文)

import os

from flask import Flask, request, abort
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__)

configuration = Configuration(access_token=os.environ["LINE_CHANNEL_ACCESS_TOKEN"])
handler = WebhookHandler(os.environ["LINE_CHANNEL_SECRET"])


@app.route("/health", methods=["GET"])
def health():
    return "OK"


@app.route("/callback", methods=["POST"])
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 handle_message(event):
    reply = f"「{event.message.text}」を受け取りました!"
    with ApiClient(configuration) as api_client:
        MessagingApi(api_client).reply_message(
            ReplyMessageRequest(
                reply_token=event.reply_token,
                messages=[TextMessage(text=reply)],
            )
        )


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000)

やっていることは3つだけです。/callback でLINEからのPOSTを受け、X-Line-Signature ヘッダーをChannel secretで検証し(この検証がSDKに入っているので、自前実装は不要です)、テキストメッセージが来たら「「◯◯」を受け取りました!」と返します。

なお、このコードはローカル開発用の最小例です。本番運用では、Webhookへの応答(200)を速やかに返し、イベントの処理は非同期にする構成がLINE公式ドキュメントで推奨されています。

手順2の鍵を環境変数に入れて起動します。

$env:LINE_CHANNEL_SECRET = '(Channel secret)'
$env:LINE_CHANNEL_ACCESS_TOKEN = '(チャネルアクセストークン)'
python app.py

http://localhost:5000/health が OK を返せば起動しています。この時点ではまだlocalhostの中の話で、LINEからは届きません。

手順4: ローカルに「変わらない公開URL」を付ける

ここが本題です。localhost:5000に、再起動しても変わらないHTTPSのURLを付けます。

この記事では、当社が開発・運営しているWireCanalで進めます(自社サービスなので、そこは割り引いて読んでください)。

なお、固定のURLを使うこと自体はWireCanal固有の話ではなく、他のトンネルサービスの固定ドメイン機能でも同じ考え方で実現できます。

ダッシュボードでcanal(トンネルの公開口)を作ります。種類はHTTP、転送先に localhost:5000 を指定すると、公開URLが1本発行されます。

canal詳細画面
canal詳細画面

このURLはランダムな文字列で発行されますが、起動のたびに変わることはありません。 PCを再起動しても、エージェントを止めて翌日つなぎ直しても、同じURLで受けられます。プランによる違いだけ正確に書いておくと、無料プランの公開URLは「使っている限り有効」(エージェントの最終接続から72時間で失効)で、毎日開発する分には同じURLのままです。3日以上止めても失効しない永続URLにしたい場合はライトプラン以上になります(筆者はライトプランで検証しました。スクリーンショットの「予約(永続)」はその表示です)。

次に、PCへエージェントを入れて起動します。セットアップガイド(wirecanal.com)のとおり、PowerShellで1行です。

irm https://download.wirecanal.com/install.ps1 | iex

ダッシュボードから接続設定ファイル(wirecanal.json)をダウンロードしてエージェントと同じフォルダに置き、起動します。

.\wirecanal.exe -config wirecanal.json

ダッシュボードに「接続されました!」と出れば、公開URLへのアクセスがlocalhost:5000へ届く状態です。

セットアップタブ・接続中
セットアップタブ・接続中

外から確認してみます。

> curl https://7pqvyoih.ja100.wirecanal.com/health
OK

手順3のFlaskが、インターネット越しに応答しました。受信ポートの開放もVPNも使っていません(エージェントは外向きの接続だけで通信します)。

手順5: Webhook URLを設定して検証する

LINE Developersコンソールに戻り、チャネルの「Messaging API設定」タブでWebhook URLを設定します。公開URLに、app.pyで受けているパス /callback を付けたものです。

https://(あなたの公開URL)/callback

設定したら、その下の「Webhookの利用」を必ずオンにします。URLを入れただけではWebhookは届きません。

Webhook設定
Webhook設定

「検証」ボタンを押すと、LINEのサーバーからこのURLへ実際にテスト接続が飛びます。「成功」が出れば、LINE→トンネル→ローカルのFlaskまでの経路が通っています。

検証成功
検証成功

つまずきポイント: 返事が二重に届く

これで動くはずなのですが、実際にスマホからメッセージを送ると、筆者の環境では奇妙なことが起きました。Botの返事の前に、身に覚えのない定型文が届くのです。

メッセージありがとうございます!申し訳ありませんが、このアカウントでは個別のお問い合わせを受け付けておりません。

正体は、公式アカウントに最初から入っている応答メッセージ(自動応答)です。筆者が新規作成したアカウントでは「Default」という一律応答が有効になっており、Webhookとは別枠で動くため、Botの返事と定型文の両方がユーザーに届いていました。

二重返信が起きたら、Official Account Managerで応答メッセージの状態を確認します。止め方は2つあり、応答メッセージ一覧で個別のメッセージを利用停止にするか、「設定」→「応答設定」で機能全体の「応答メッセージ」をオフにします。いま実際に送られる状態かどうかは、応答メッセージ一覧の上部にある「現在のステータス」で確認できます(「利用中。応答メッセージは送信されています」と出ていたら、まだ送られます)。筆者はここのステータスが「利用中」のままなのに気づかず、1回むだにテストしました。

スマホから使ってみる

友だち追加用のQRコード(Official Account Managerの「友だち追加ガイド」にあります)をスマホのLINEで読み取り、Botを友だち追加してメッセージを送ります。

iPhone実機。「こんにちは」に対してBotが即座に返信している
iPhone実機。「こんにちは」に対してBotが即座に返信している

「こんにちは」と送ると、即座に「「こんにちは」を受け取りました!」が返ってきます。サーバー側のログにも、LINEからのPOSTが記録されています。

127.0.0.1 - - [11/Aug/2026 00:24:32] "POST /callback HTTP/1.1" 200 -

ここまでで、スマホのLINE→LINEプラットフォーム→固定の公開URL→ローカルPCのFlask→返信、という経路が全部つながりました。

まとめ: URLを書き換える作業は、もう発生しない

この構成のいちばんの利点は、翌日の開発再開が「エージェントとapp.pyを起動するだけ」になることです。公開URLが変わらないので、LINE Developersコンソールを開き直してWebhook URLを貼り替える作業がそもそも発生しません。コードを直したらapp.pyを再起動するだけで、同じURLのまま試せます。

最後に、今回の構成の勘所を置いておきます。

勘所内容
Messaging APIチャネルの作り方現行はLINE公式アカウント経由(コンソールから直接は作れない・電話番号認証が必要)
Webhookが届かないとき「Webhookの利用」がオンか、「検証」が成功するかを先に見る
返事が二重に届くとき応答メッセージをオフにする(一覧上部の「現在のステータス」で確認)
URLの書き換え公開URLは起動のたびに変わらない(無料は使っている限り有効・ライト以上は永続)。Webhook URL設定は最初の1回だけ

それでは、また次回、お会いしましょう!

出典・参考

関連記事

Read more

CEATEC 2026に出展します!AIアバター動画生成「MotionVox®」の最新版を実体験いただけます

CEATEC 2026に出展します!AIアバター動画生成「MotionVox®」の最新版を実体験いただけます

株式会社Qualitegは、2026年10月13日(火)~16日(金)に幕張メッセで開催される「CEATEC 2026」に出展いたします。今回の出展では、当社が開発したAIアバター動画生成サービス「MotionVox®」の最新版を中心に、動画生成の実演と作例をご紹介いたします。 昨年の CEATEC 2025 に続き、2年連続の出展となります。昨年の会場の様子は 出展レポート にまとめております。 出展概要 * 会期 2026年10月13日(火)~10月16日(金)10:00~17:00 * 会場 幕張メッセ(千葉県千葉市美浜区中瀬2-1) * 出展エリア ネクストジェネレーションパーク * ブース番号 ホール4 4H339 * 入場 無料(来場事前登録制) * CEATEC内特設サイト https://www.ceatec.com/nj/exhibitor_

By Qualiteg プロダクト開発部
ゼロから作るコーディングエージェント【第3回】「止まらなかった」は「進んだ」ではない。空転を数え、思考は切らずに上限だけ置く

ゼロから作るコーディングエージェント【第3回】「止まらなかった」は「進んだ」ではない。空転を数え、思考は切らずに上限だけ置く

300ターン走ってAPIが0本。「モデルの限界」と結論を書いた翌日、記録を読み直すと234ターンがツールを1度も呼んでいませんでした。空転率という指標、1つ塞ぐと次の形に移る空転、思考をオフにすると95点が71点に落ちて時間も縮まなかった話、出力枠を広げたら反対側を数える話を書きます。

By Qualiteg プロダクト開発部
Ubuntuの自動更新でカーネルだけ上がり、再起動したらGPUが消えた。NVIDIAモジュールが置き去りになる原因と3つの対策

Ubuntuの自動更新でカーネルだけ上がり、再起動したらGPUが消えた。NVIDIAモジュールが置き去りになる原因と3つの対策

Ubuntu 24.04のGPUサーバーを再起動したらnvidia-smiが動かない。原因はunattended-upgradesがカーネルだけを更新し、NVIDIAモジュールが依存関係の都合で見送られていたこと。ログで原因を突き止め、1コマンドで復旧し、apt-mark holdを含む3つの再発防止策を比べて採用した記録。

By Qualiteg プロダクト開発部
ゼロから作るコーディングエージェント【第2回】完了は「作った」ではなく「動いた」で決める。完了の門と、押し戻しに書くこと

ゼロから作るコーディングエージェント【第2回】完了は「作った」ではなく「動いた」で決める。完了の門と、押し戻しに書くこと

モデルの「完了しました」は5つの形ですり抜けました。構文検査だけ、テストは通るが動かない、待ち受けは立つが最初の要求で落ちる、起動するが仕様のAPIが無い、assertの無いテスト。完了の門を3段で閉じる設計と、押し戻しに「落ちた行と調べ方」を書いて「答え」を書かない理由を、数字つきで書きます。

By Qualiteg プロダクト開発部