Một URL webhook mà Meta thực sự chấp nhận.

Meta kiểm tra callback URL trước khi chịu lưu nó, rồi ngừng thử sau 36 giờ nếu không ai xác nhận (ack). AnyHook trả lời bước kiểm tra, lưu mọi sự kiện và thử lại (retry) tới handler (đoạn code xử lý webhook trên server của bạn) theo lịch riêng, còn mọi sự kiện đều phát lại được chừng nào gói của bạn còn giữ nó.

Bài toán thời gian

Meta gửi một GET tới callback URL của bạn, kèm hub.mode=subscribe và một challenge, và sẽ không lưu URL nếu bạn không trả lại đúng challenge đó làm toàn bộ body của response. Một tunnel giúp bạn qua được bước này một lần. Lần tới ngrok cấp cho bạn hostname mới, bạn lại phải vào App Dashboard làm lại từ đầu, và subscription đã tạo trước đó trỏ vào hư không.

Khi sự kiện bắt đầu đổ về, thời hạn không tính theo từng request mà là 36 giờ. Meta thử lại với tần suất giảm dần trong khoảng đó rồi bỏ mọi thứ chưa từng được xác nhận. Với một bot tự động trả lời, khoảng thời gian đó là ranh giới giữa trả lời được một bình luận và không bao giờ biết nó tồn tại.

36 giờ
Trước khi Meta bỏ một thông báo chưa được xác nhận
1 URL
Dùng chung cho Instagram, Facebook và Threads
GET
Bước bắt tay AnyHook trả lời thay bạn
0
Lần xác minh lại sau khi tunnel khởi động lại

Trỏ callback về AnyHook, xác minh một header

URL nhận (inbound URL) không bao giờ đổi, nên App Dashboard chỉ cần cấu hình một lần. AnyHook kiểm tra X-Hub-Signature-256 của Meta tại edge (lớp máy chủ biên của Cloudflare), lưu sự kiện, rồi ký lại lượt gửi tới handler của bạn.

// 1. In AnyHook: set the app's source to Meta, paste your App Secret.
//    Optionally set a Verify Token. You invent that string, it is not
//    something you copy out of Meta.
//
// 2. In Meta App Dashboard -> Webhooks:
//    Callback URL: https://in.anyhook.net/{user-slug}/{app-slug}
//    Verify Token: the same string you chose
//
//    AnyHook answers the GET handshake, so the URL saves first time.

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();
  const change = event.entry?.[0]?.changes?.[0];

  // Meta already has its acknowledgement. Take as long as you need here.
  if (change?.field === "comments") {
    await replyToComment(change.value);   // an LLM call, a few seconds
  }

  return new Response("ok", { status: 200 });
}

Câu hỏi các team thường đặt ra

Meta báo callback URL không xác minh được. Vì sao?
Bước kiểm tra đó là một GET kèm hub.mode=subscribe và hub.challenge, và nó thất bại nếu URL của bạn không trả lại challenge làm toàn bộ body, không bọc trong JSON và không có ký tự xuống dòng ở cuối. AnyHook tự trả lời bước này, nên URL nhận lưu được ngay lần đầu. Nếu bạn đặt Verify Token trong AnyHook, hãy nhập đúng chuỗi đó vào App Dashboard.
Instagram, Facebook và Threads có cần ba lần thiết lập riêng không?
Không. Cả ba dùng chung một cơ chế webhook của Graph API với cùng chữ ký X-Hub-Signature-256, nên một App (một URL nhận cho một nguồn webhook, ví dụ “Stripe production”) trên AnyHook dùng được cho cả ba. Chọn các subscription bạn cần trong App Dashboard.
Bình luận và DM sẽ ra sao khi bot đang deploy lại?
AnyHook lưu sự kiện ngay khi nó tới và thử lại endpoint của bạn theo backoff, nên một lần deploy hay một lần crash không làm bạn mất tin nhắn. Meta chỉ thấy một lần xác nhận và không bận tâm thêm.
Verify Token có phải là App Secret không?
Không, và hai giá trị này không nên là cùng một chuỗi. App Secret ký mọi payload và không bao giờ đi trong URL. Verify Token là chuỗi bạn tự đặt, nó tới trong query string của bước bắt tay khi thiết lập và chỉ được dùng đúng lúc đó. Đặt App Secret vào query string sẽ làm nó lộ ra trong mọi log trên đường đi.

Đổi một URL. Giữ nguyên handler Instagram của bạn.

Gói Free gồm 10.000 sự kiện / tháng. Không cần SDK: trỏ Instagram về URL nhận webhook của bạn trên AnyHook.