[ #LINEDC ] HonoでLINE Message API用の Webhook APIを作る
この記事では、HonoフレームワークでLINE Message APIのWebhook APIを作成する方法を解説しています。署名検証の実装方法や、メッセージ処理関数の分離、リクエストボディの取り扱いなどのポイントが紹介されています。LINEボットを構築する際の参考になる実践的な内容です。
目次
LINE上でChatbotを作るには、LINEが受信したメッセージを転送するためのWebhook APIが必要です。一般的には、POSTリクエストを受け付けるパブリックなAPIを作ることになります。今回はHonoでAPIを提供する際の署名検証処理系をまとめてみました。
最終コード
コードがあればよい。という方向けに、全体像です。TypeScriptですので、JavaScriptで実装される方は型情報を取り除いてお使いください。
import { Hono } from 'hono';
import { validateSignature, WebhookEvent } from '@line/bot-sdk';
interface LineWebhookRequest {
destination: string;
events: WebhookEvent[];
}
type Env = {
LINE_CHANNEL_SECRET: string;
};
const app = new Hono<{ Bindings: Env }>();
// LINE Webhook処理
app.post('/line', async (c) => {
try {
const signature = c.req.header('X-Line-Signature');
if (!signature) {
return c.text('Signature required', 401);
}
// リクエストボディを検証
const body = await c.req.text();
const isValid = validateSignature(body, c.env.LINE_CHANNEL_SECRET, signature);
if (!isValid) {
return c.text('Invalid signature', 401);
}
// Webhookイベントを処理
const webhookRequest = JSON.parse(body) as LineWebhookRequest;
const events = webhookRequest.events;
console.log(`LINEイベント受信: ${events.length}件`);
// イベントごとに処理
for (const event of events) {
// @TODO メッセージごとの処理を書く
}
return c.text('OK');
} catch (error) {
console.error('LINE Webhookエラー:', error);
return c.text('Internal Server Error', 500);
}
});
Webhookを実装する際のポイント
検証系やメッセージ処理系を作るにあたって、意識しておくと良いかなと思った部分を2つ紹介します。
ポイント1: メッセージ処理関数は、別で用意する
LINEのWebhookは、1通ごとにイベントが届くわけではない様子です。というのも、Webhookで送信されるデータを見ると、イベントデータが配列型式になっています。そのため、実際の処理系統は、下のコードのようにループの中で実行することになります。
for (const event of events) {
// @TODO メッセージごとの処理を書く
}
HonoでAPIを登録する部分( app.post()
)には、署名検証系のみ実装しておきましょう。これによってリクエストの検証とメッセージ処理の責務が分担できますので、コードの見通しもよくなります。
ポイント2: リクエストBodyは一旦テキストにする
JSONでデータが飛んでくるため、このタイプのAPIを作る時はawait c.req.json()
でリクエスト内容を取得したくなります。ですがStripeと同様に、LINEでもWebhookの署名検証処理を動かすステップではテキストデータが必要となります。そのためリクエスト内容は、await c.req.text()
で取得するようにしましょう。
// リクエストボディを検証
const body = await c.req.text();
const isValid = validateSignature(body, c.env.LINE_CHANNEL_SECRET, signature);
if (!isValid) {
return c.text('Invalid signature', 401);
}
検証が終わった後は、JSON.parse
でテキストからオブジェクトに変換してやります。
const webhookRequest = JSON.parse(body) as LineWebhookRequest;
const events = webhookRequest.events;
二度手間に見えますが、セキュリティ対策としても重要ですので、意識しておくと良さそうです。