Meta が受け付けてくれる Webhook URL。

Meta はコールバック URL を保存する前に検証し、応答がなければ 36 時間でリトライをやめます。AnyHook は検証に応答し、すべてのイベントを保存し、AnyHook 自身のスケジュールでハンドラーへのリトライを続けます。プランで保存されているあいだは、どのイベントも再送できます。

タイミングの問題

Meta はコールバック URL に hub.mode=subscribe とチャレンジを付けた GET を送り、そのチャレンジをレスポンスボディ全体としてそのまま返さない限り URL を保存しません。トンネルを使えば一度は通ります。しかし次に ngrok が新しいホスト名を割り当てると、アプリダッシュボード(App Dashboard)でまた同じ作業をすることになり、作成済みのサブスクリプションはどこも指していない状態になります。

イベントが流れ始めると、期限はリクエストごとではなく 36 時間になります。Meta はその間、頻度を下げながらリトライし、最後まで応答のなかったものは破棄します。自動返信ボットにとって、この期間はコメントに返信できるか、コメントがあったことすら知らずに終わるかの分かれ目です。

36 時間
Meta が応答のない通知を破棄するまで
URL 1 つ
Instagram、Facebook、Threads に対応
GET
AnyHook が代わりに応答するハンドシェイク
0
トンネルの再起動後に必要な再検証

コールバックを AnyHook に向け、ヘッダーを 1 つ検証する

受信 URL は変わらないので、アプリダッシュボードでの設定は一度で済みます。AnyHook は Meta の X-Hub-Signature-256 をエッジで検証してイベントを保存し、ハンドラーへの配信を再署名します。

// 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 });
}

よくある質問

Meta でコールバック URL を検証できないと表示されます。なぜですか?
この検証は hub.mode=subscribe と hub.challenge を付けた GET です。URL がチャレンジをボディ全体としてそのまま返さないと失敗します。JSON で包んだり、末尾に改行を付けたりしてもいけません。AnyHook がこれに応答するので、受信 URL は一度で保存できます。AnyHook で Verify Token を設定した場合は、アプリダッシュボードにも同じ文字列を入力してください。
Instagram、Facebook、Threads で 3 つ別々の設定が必要ですか?
いいえ。どれも同じ X-Hub-Signature-256 署名を使う、1 つの Graph API Webhook の仕様です。そのため AnyHook の App(Webhook の送信元ごとに作る受信設定)1 つで 3 つすべてに対応できます。どのサブスクリプションを使うかは、アプリダッシュボードで選びます。
ボットの再デプロイ中に届いたコメントや DM はどうなりますか?
AnyHook はイベントが届いた時点で保存し、バックオフしながらエンドポイントへリトライします。そのため、デプロイやクラッシュでメッセージを失うことはありません。Meta には応答が 1 回返るだけなので、Meta 側の処理はそれで完了します。
Verify Token と App Secret は同じものですか?
いいえ。同じ文字列にするべきでもありません。App Secret はすべてのペイロードの署名に使われ、URL に載ることはありません。Verify Token は自分で決める文字列で、設定時のハンドシェイクのクエリ文字列で届き、そのときにしか使われません。App Secret をクエリ文字列に入れると、経路上のあらゆるログに漏れることになります。

変えるのは URL 1 つだけ。Instagram のハンドラーはそのまま。

Free プランには月 10,000 件が含まれます。SDK は不要です。Instagram の Webhook を AnyHook の受信 URL に向けるだけです。