Một URL webhook được nút Verify chấp nhận ngay lần đầu.
Console chỉ nhận URL https và nút Verify đòi mã 200, redelivery tắt nếu bạn không tìm ra công tắc, và LINE không bao giờ nói bao lâu thì nó thử lại một lần. AnyHook trả lời Verify, lưu mọi sự kiện tại edge (lớp máy chủ biên của Cloudflare) và tiếp tục thử lại (retry) tới handler (đoạn code xử lý webhook trên server của bạn) theo một lịch mà bạn thực sự nhìn thấy.
Bài toán thời gian
LINE Developers Console không lưu Webhook URL nào không phải https, và nút Verify bên cạnh gửi một POST thật, phải nhận về 200. Một tunnel qua được bước kiểm tra cho tới khi hostname đổi, lúc đó bot của bạn trỏ vào hư không và console sẽ không báo cho bạn. Vấn đề sâu hơn là chuyện gì xảy ra khi một lượt gửi bị lỡ: webhook redelivery mặc định tắt, và ngay cả khi bật, LINE nói thẳng rằng số lần thử lại và khoảng cách giữa các lần không được công bố và có thể thay đổi mà không báo trước.
Phía trả lời có đồng hồ riêng. Một reply token chỉ có hiệu lực trong một phút và dùng được một lần, nên sự kiện mà server của bạn nhận muộn, sau một lần crash, một lần deploy hay một lần thử lại, sẽ không thể trả lời bằng lệnh gọi reply miễn phí nữa. Nếu server bot không liên lạc được đủ lâu, LINE có thể ngừng hẳn gửi webhook tới nó, và ngưỡng cho chuyện đó cũng không được công bố.
Trỏ console về AnyHook, xác minh một header
Đặt nguồn của App (một URL nhận cho một nguồn webhook, ví dụ “Stripe production”) là LINE và dán Channel Secret: AnyHook kiểm tra x-line-signature tại edge (HMAC-SHA256 dạng base64 của raw body), và request giả mạo không bao giờ tới được bạn hay bị tính vào hạn mức của bạn. Mỗi lượt gửi đều được ký lại bằng một AnyHook-Signature mới.
// 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 });
}Câu hỏi các team thường đặt ra
- Nút Verify trong console cứ thất bại. Nó cần gì?
- Nó gửi một POST với mảng events rỗng tới URL và chờ một mã 200 trơn qua https. Tunnel trên máy local lúc qua lúc không, và hỏng khi hostname đổi. URL nhận (inbound URL) của AnyHook là một endpoint https ổn định, trả lời ngay khi request đã được đưa vào hàng đợi bền vững, nên Verify qua ngay lần đầu và những lần sau vẫn qua. Thêm một điều nên biết: LINE ký POST xác minh bằng Channel Secret của bạn như mọi webhook khác, nên nếu nguồn đã đặt là LINE mà Verify vẫn thất bại, nguyên nhân thường là secret bị gõ sai. AnyHook từ chối các request đó tại edge với mã 401: trang của App đếm số lần, và 5 request đầu tiên mỗi ngày được giữ trong nhật ký sự kiện với trạng thái bị từ chối, kèm cả header, để bạn thấy lỗi. Dán lại secret rồi bấm Verify lần nữa.
- Ai xác minh x-line-signature, AnyHook hay server của tôi?
- AnyHook, tại edge, khi nguồn của App là LINE và đã đặt Channel Secret: request không qua bước kiểm tra bị từ chối trước khi tới server của bạn hay tiêu tốn hạn mức. Sau đó server của bạn chỉ cần xác minh một header AnyHook-Signature thống nhất. Header x-line-signature gốc vẫn được chuyển tiếp nguyên vẹn, và vì cơ chế của LINE chỉ ký body, không có dấu thời gian (timestamp), nó vẫn hợp lệ ở các lần thử lại nếu bạn muốn kiểm tra thêm ở downstream.
- Reply token sẽ ra sao khi một lượt gửi được thử lại?
- Gần như chắc chắn nó đã hết hạn, vì token chỉ sống một phút kể từ webhook gốc. Cách làm hiệu quả: trả lời bằng endpoint reply ở lần gửi đầu tiên, nhanh, và chuyển sang endpoint push khi lệnh gọi reply thất bại. Dù thế nào AnyHook cũng giữ toàn bộ body của sự kiện, nên một handler chạy muộn vẫn biết chính xác người dùng đã nói gì.
- Server bot của tôi chạy trên gói miễn phí và hay ngủ. Bot có còn trả lời kịp không?
- Dù sao LINE cũng nhận được mã 200 từ AnyHook sau khoảng 1 giây, nên LINE không thấy lỗi nào. Sau đó AnyHook gửi ngay sự kiện tới server của bạn và chờ nó thức dậy, tối đa 60 giây trên gói Free. Nếu server trả lời trong khoảng đó, sự kiện tới ngay ở lần gửi đầu tiên, trễ vài giây, và reply token, có hiệu lực một phút kể từ sự kiện gốc, thường vẫn dùng được. Nếu server thức dậy lâu hơn một phút của token, phương án push dự phòng trong đoạn code ở trên sẽ trả lời thay. Nếu server hoàn toàn không trả lời kịp, lần gửi tiếp theo diễn ra sau 5 phút.
- Tôi viết bot bằng Python. Kiểm tra AnyHook-Signature thế nào?
- Header có dạng t=<unix seconds>,v1=<hex>. Tính HMAC-SHA256 của f"{t}.{raw_body}" bằng signing secret (khóa bí mật dùng để ký) của đích (App → Destinations → Signing secret trong dashboard), so sánh với v1 bằng hmac.compare_digest, và từ chối t cũ hơn 5 phút. Nếu handler của bạn đã kiểm tra x-line-signature, bạn có thể giữ nguyên cách đó: LINE chỉ ký riêng body, nên chữ ký vẫn hợp lệ ở các lần thử lại.
- AnyHook đã thử lại thay tôi, vậy có nên bật redelivery của LINE nữa không?
- Có. Hai cơ chế lo hai chặng khác nhau. AnyHook thử lại chặng từ AnyHook tới server của bạn sau khi sự kiện đã được lưu. Redelivery của LINE lo chặng từ LINE tới AnyHook, điều này quan trọng trong trường hợp hiếm hoi AnyHook không lưu được một sự kiện và cố ý trả về 503 để bên gửi thử lại.
Đổi một URL. Giữ nguyên handler LINE của bạn.
Gói Free gồm 10.000 sự kiện / tháng. Không cần SDK: trỏ LINE về URL nhận webhook của bạn trên AnyHook.