Uma URL de webhook que o botão Verify aceita de primeira.
O console só aceita uma URL https e o botão Verify quer um 200, o reenvio fica desligado a menos que você encontre a opção, e o LINE nunca diz com que frequência tenta de novo. O AnyHook responde ao Verify, armazena cada evento na edge e continua tentando entregar ao seu handler num cronograma que você consegue ver.
O problema do tempo
O LINE Developers Console não salva uma Webhook URL que não seja https, e o botão Verify ao lado dela envia um POST real que precisa voltar com 200. Um túnel passa na verificação até o hostname mudar; aí seu bot fica apontando para o nada e o console não avisa. O problema mais profundo é o que acontece quando uma entrega falha: o reenvio de webhooks vem desativado e, mesmo ligado, o LINE declara abertamente que a quantidade e o intervalo das novas tentativas (retries) não são divulgados e podem mudar sem aviso.
A resposta tem um relógio próprio. Um reply token vale por um minuto e funciona uma vez, então um evento que seu servidor pega atrasado, depois de um crash, de um deploy ou de uma nova tentativa, já não pode ser respondido com a chamada de reply gratuita. Se o servidor do bot ficar inacessível por tempo suficiente, o LINE pode parar de enviar webhooks para ele de vez, e o limite para isso também não é publicado.
Aponte o console para o AnyHook e verifique um cabeçalho
Defina a origem do app como LINE e cole o Channel Secret: o AnyHook verifica o x-line-signature na edge (HMAC-SHA256 em base64 do corpo bruto), e requisições forjadas nunca chegam a você nem à sua cota. Cada entrega é assinada de novo com um AnyHook-Signature novo.
// 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.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 });
}Perguntas que as equipes fazem
- O botão Verify do console continua falhando. O que ele quer?
- Ele envia um POST com um array events vazio para a URL e espera um 200 simples via https. Túneis locais passam de forma intermitente e quebram quando o hostname muda. Uma URL de entrada do AnyHook é um endpoint https estável que responde assim que a requisição está gravada numa fila durável, então o Verify passa na primeira vez e continua passando. Mais uma coisa: o LINE assina o POST de verificação com seu Channel Secret como qualquer outro webhook, então, se a origem está definida como LINE e o Verify ainda falha, a causa comum é um segredo digitado errado. O AnyHook rejeita essas requisições na edge com um 401: a página do app as contabiliza, e as 5 primeiras de cada dia ficam no log de eventos como rejeitadas, com os cabeçalhos, para você ver a falha. Cole o segredo de novo e clique em Verify outra vez.
- Quem verifica o x-line-signature, o AnyHook ou meu servidor?
- O AnyHook, na edge, quando a origem do app é LINE e o Channel Secret está configurado: requisições que falham na verificação são rejeitadas antes de chegar ao seu servidor ou de consumir cota. Depois, seu servidor verifica um único cabeçalho padronizado, o AnyHook-Signature. O x-line-signature original continua sendo encaminhado intacto e, como o esquema do LINE assina só o corpo, sem timestamp, ele continua válido nas novas tentativas se você quiser conferir de novo no seu servidor.
- O que acontece com o reply token quando há uma nova tentativa de entrega?
- Quase certamente ele expirou: o token vale um minuto a partir do webhook original. O padrão que funciona: responda com o endpoint de reply na primeira tentativa, a rápida, e recorra ao endpoint de push quando a chamada de reply falhar. O AnyHook guarda o corpo inteiro do evento de qualquer forma, então um handler atrasado ainda sabe exatamente o que foi dito.
- O servidor do meu bot hiberna num plano gratuito. As respostas ainda saem a tempo?
- O LINE recebe o 200 do AnyHook em cerca de um segundo de qualquer forma, então o LINE não vê nenhuma falha. Em seguida, o AnyHook envia o evento ao seu servidor na hora e espera ele acordar, por até 60 s no Free. Se ele responder nesse prazo, o evento chega na primeira tentativa, alguns segundos atrasado, e o reply token, válido por um minuto a partir do evento original, normalmente ainda funciona. Se o servidor demorar mais que o minuto do token para acordar, o fallback de push do código acima responde no lugar. Se seu servidor não responder a tempo de forma alguma, a próxima tentativa acontece 5 minutos depois.
- Escrevo meu bot em Python. Como verifico o AnyHook-Signature?
- O cabeçalho tem o formato t=<unix seconds>,v1=<hex>. Calcule o HMAC-SHA256 de f"{t}.{raw_body}" com o segredo de assinatura do destino (App → Destinations → Signing secret no painel), compare com v1 usando hmac.compare_digest e rejeite um t com mais de 5 minutos. Se você já verifica o x-line-signature no seu handler, pode continuar fazendo isso: o LINE assina só o corpo, então ele continua válido nas novas tentativas.
- Ainda devo ativar o reenvio do próprio LINE se o AnyHook tenta de novo por mim?
- Sim. Eles cobrem trechos diferentes. O AnyHook tenta de novo o trecho do AnyHook até seu servidor, depois que o evento está armazenado. O reenvio do LINE cobre o trecho do LINE até o AnyHook, o que importa no caso raro em que o AnyHook não consegue gravar um evento e responde 503 de propósito para o remetente tentar de novo.
Troque uma URL. Mantenha seu handler de LINE.
O plano Free cobre 10 mil eventos / mês. Sem SDK: aponte os webhooks de LINE para sua URL de entrada do AnyHook.