LINE Messaging API

A webhook URL the Verify button accepts first time.

The console demands an https URL that answers 200 before it will save anything, redelivery is off unless you find the toggle, and LINE never says how often it retries. AnyHook answers the Verify, stores every event at the edge, and keeps retrying your handler on a schedule you can actually see.

The timing problem

LINE Developers Console will not save a Webhook URL that is not https, and the Verify button next to it sends a real POST that must come back 200. A tunnel passes the check until the hostname rotates, then your bot is pointing at nothing and the console will not tell you. The deeper problem is what happens on a miss: webhook redelivery ships disabled, and even switched on, LINE states outright that the retry count and interval are undisclosed and can change without notice.

The reply side has its own clock. A reply token is valid for one minute and works once, so an event your server picks up late, after a crash, a deploy, or a retry, can no longer be answered with the free reply call. If the bot server stays unreachable long enough, LINE may stop sending webhooks to it altogether, and the threshold for that is unpublished too.

Off
Redelivery's factory setting, a missed webhook is gone
200
What the Verify button must see before the URL saves
1 min
Reply-token lifetime, single use
?
Retry count and interval LINE publishes: none

Point the console at AnyHook, verify one header

Set the app's source to LINE and paste the Channel Secret: AnyHook checks x-line-signature at the edge (base64 HMAC-SHA256 of the raw body) and forged requests never reach you or your quota. Every delivery is re-signed with a fresh AnyHook-Signature.

// 1. In AnyHook: set the app's source to LINE, paste your Channel Secret.
//
// 2. In LINE Developers Console -> Messaging API:
//    Webhook URL: https://in.anyhook.net/{user-slug}/{app-slug}
//    Press Verify (AnyHook answers 200), enable "Use webhook",
//    and turn ON "Webhook redelivery" while you are there.

import { verifyWebhook } from "anyhook-verify";

export async function POST(req: Request) {
  const ok = await verifyWebhook(req, process.env.ANYHOOK_SIGNING_SECRET!);
  if (!ok) return new Response("invalid signature", { status: 401 });

  const event = await req.json();
  for (const e of event.payload.events ?? []) {
    if (e.type !== "message") continue;

    const answer = await draftAnswer(e.message); // an LLM call, a few seconds

    // Reply tokens die after one minute and work once. On a retried
    // delivery the token is already dead, so fall back to push instead
    // of dropping the answer on the floor.
    try {
      await lineReply(e.replyToken, answer);
    } catch {
      await linePush(e.source.userId, answer);
    }
  }
  return new Response("ok", { status: 200 });
}

Questions teams ask

The Verify button in the console keeps failing. What does it want?
It sends a POST with an empty events array to the URL and expects a bare 200 over https. Local tunnels pass it intermittently and break when the hostname rotates. An AnyHook inbound URL is a stable https endpoint that answers once the request is durably queued, so Verify passes the first time and keeps passing. One more thing to know: LINE signs the verification POST with your Channel Secret like any other webhook, so if the source is set to LINE and Verify still fails, the usual cause is a mistyped secret. AnyHook rejects those at the edge with a 401, and rejected requests never appear in the event log, the app's settings page shows an edge-reject banner instead. Re-paste the secret and press Verify again.
Who verifies x-line-signature, AnyHook or my server?
AnyHook, at the edge, when the app's source is LINE and the Channel Secret is set: requests that fail the check are rejected before they reach your server or spend quota. Your server then verifies one uniform AnyHook-Signature header. The original x-line-signature is still forwarded untouched, and because LINE's scheme signs only the body, with no timestamp, it stays valid on retries if you want to double-check downstream.
What happens to the reply token when a delivery is retried?
It is almost certainly expired, the token lives one minute from the original webhook. The pattern that works: answer with the reply endpoint on the first, fast attempt, and fall back to the push endpoint when the reply call fails. AnyHook keeps the full event body either way, so a late handler still knows exactly what was said.
Should I still enable LINE's own redelivery if AnyHook retries for me?
Yes. They cover different legs. AnyHook retries the trip from AnyHook to your server after the event is stored. LINE's redelivery covers the trip from LINE to AnyHook, which matters in the rare case AnyHook cannot persist an event and deliberately answers 503 so the sender tries again.

Change one URL. Keep your LINE handler.

Free tier covers 3K events / month. No SDK, no code changes, just point LINE at your AnyHook inbound URL.