LoginSignup
10
5

More than 1 year has passed since last update.

LINE BOT APIのreplyMessageでたまにInvalid reply tokenが発生する

Last updated at Posted at 2021-09-11

概要

line-bot-sdk-javaを使ってLINE BOTを作ったが、たまにInvalid reply tokenが発生し返信できないことがある。

この記事ではJavaの場合の解決方法を書くが、他の言語でも原因は同じである。
ちなみに、Herokuの無料hobbyプランを使っている。

エラー内容

ERROR 4 --- [io-33152-exec-3] c.l.b.s.b.s.LineMessageHandlerSupport : InvocationTargetException occurred.
Caused by: java.lang.RuntimeException: java.util.concurrent.ExecutionException: com.linecorp.bot.client.exception.BadRequestException: Invalid reply token : ErrorResponse(requestId=xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, message=Invalid reply token, details=[])

Caused by: java.util.concurrent.ExecutionException: com.linecorp.bot.client.exception.BadRequestException: Invalid reply token : ErrorResponse(requestId=xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, message=Invalid reply token, details=[])

Caused by: com.linecorp.bot.client.exception.BadRequestException: Invalid reply token : ErrorResponse(requestId=xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, message=Invalid reply token, details=[])

原因

公式のドキュメントには以下のように書いてある。

応答トークンは一定の期間が経過すると無効になるため、メッセージを受信したらすぐに応答を返す必要があります。応答トークンは1回のみ使用できます。
応答メッセージを送る

LINE BOTのreplyMessage(応答メッセージ)は、送信されたメッセージ1つに付きreply tokenが発行され、それを使ってBOTが返信するという仕組みになっている。そして、このreply tokenは一定期間経過すると無効になり、そうするとInvalid reply tokenが発生し返信ができない、というのが原因のようである。

以下の記事で、このtokenがどのくらいの時間で無効になるのか検証されているが、30秒らしい。

エラーが起こる条件

原因はわかったが、なぜこのエラーが発生するのか。

このBOTはHerokuの無料・ホビーのプランで運用している。
https://jp.heroku.com/pricing

この無料のプランは、インスタンスに30分アクセスがない場合自動でsleep状態になる。
そして再度アクセスがあった場合に、起動し処理を実行する。

sleep状態であっても、受けたリクエストに対する処理は実行してくれるのだが、sleepからrunning状態になるのに時間がかかるため、sleep開けの初回リクエストに対するレスポンスは、通常よりも少し時間がかかってしまう。

つまり今回のエラーは、LINEでユーザーがBOTにメッセージを送った際、Herokuがsleep状態であるとHerokuが起動する時間がかかるため、その分BOTからの返信に時間がかかる。その間にreply tokenの有効期限が過ぎてしまうので、Invalid reply tokenが発生するということになる。

対策

対策には2つある。

1. Herokuを有料プランにする

有料プランにすればsleep状態にはならないので、お金で解決する。
https://jp.heroku.com/pricing

2. pushMessageを使う

Invalid reply tokenが発生したら、pushMessageを使って再送信する方法で解決する。

LINE BOTでメッセージを送信する方法には、大きく分けて、先述したreplyMessage(応答メッセージ)と、このpushMessage(プッシュメッセージ)の2つがある。

replyMessageはreply tokenが必要だが、pushMessageにはreply tokenは不要なので、任意のタイミングでメッセージを送信することができる。

つまり、replyMessageでInvalid reply tokenが発生したら、pushMessageを使ってリトライする、という方法で解決する。

ちなみにpushMesasgeにはreply tokenは不要だが、グループに返信する場合にはgroupId、個別ユーザーへの場合はuserIdが必要なので、送信する際にはいずれかを指定すること。

KitchenSinkController.java
@Slf4j
@LineMessageHandler
public class KitchenSinkController {
    // 一部省略

    // replyMessageでの返信
    private void replyMessage(String replyToken, List<Message> replyMessage) throws Exception {
        try {
            // 返信処理
            BotApiResponse apiResponse = lineMessagingClient
                    .replyMessage(new ReplyMessage(replyToken, replyMessage, this.notificationDisabled))
                    .get();
            log.info("Sent messages: {}", apiResponse);
        } catch (InterruptedException | ExecutionException e) {
            //throw new RuntimeException(e);
            // Invalid reply tokenが発生した場合ここでcatchされるので、pushMessage処理を実行する。

            // toにはuserIdもしくはgroupIdを入れる(userId、groupIdをプロパティにsetする処理は省略)
            String to = (this.groupId.isEmpty()) ? this.userId : this.groupId;
            pushMessage(to, replyMessage);
        }
    }

    // pushMessageでの送信。toにはuserIdもしくはgroupIdを入れる
    private void pushMessage(String to, List<Message> replyMessage) throws Exception {
        try {
            BotApiResponse apiResponse = lineMessagingClient
                    .pushMessage(new PushMessage(to, replyMessage, this.notificationDisabled))
                    .get();
            log.info("Sent push messages to: {}", to);
            log.info("Sent push messages: {}", apiResponse);
        } catch (InterruptedException | ExecutionException e) {
            // pushMesageでもだめならExceptionを投げる
            throw new RuntimeException(e);
        }
    }
}

ちなみにuserIdは以下で取得できる。

String userId = event.getSource().getUserId();

groupIdはグループチャットでない場合は存在しないため、取得方法が少し面倒だがこんな感じ。

String groupId;
if (event.getSource() instanceof GroupSource) {
    groupId = ((GroupSource) event.getSource()).getGroupId();
} else {
    groupId = "";
}

pushMessageの詳細については以下の公式ドキュメントを参照。

まとめ

この記事ではJavaの解決策を書いたが、他の言語でも原因は同じため、pushMessageでリトライするようにすれば解決できる。

関連

10
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5