Uma URL de webhook que a Meta aceita de verdade.

A Meta verifica a URL de callback antes de salvá-la e depois para de tentar após 36 horas se ninguém confirmar o recebimento. O AnyHook responde à verificação, armazena cada evento e tenta de novo entregar ao seu handler segundo o próprio cronograma, com cada evento disponível para replay enquanto o seu plano o guarda.

O problema do tempo

A Meta envia um GET para sua URL de callback com hub.mode=subscribe e um challenge, e não salva a URL a menos que você devolva esse challenge como o corpo inteiro da resposta. Um túnel resolve isso uma vez. Na próxima vez que o ngrok der a você um novo hostname, você volta ao App Dashboard para fazer tudo de novo, e a inscrição que você já criou fica apontando para o nada.

Quando os eventos começam a chegar, o prazo não é por requisição: é de 36 horas. A Meta tenta de novo com frequência decrescente nessa janela e depois descarta tudo o que nunca teve o recebimento confirmado. Para um bot de resposta automática, essa janela é a diferença entre responder a um comentário e nunca saber que ele existiu.

36 h
Antes de a Meta descartar uma notificação não confirmada
1 URL
Cobre Instagram, Facebook e Threads
GET
Handshake que o AnyHook responde por você
0
Novas verificações depois que um túnel reinicia

Aponte o callback para o AnyHook e verifique um cabeçalho

A URL de entrada nunca muda, então o App Dashboard vira uma etapa única. O AnyHook verifica o X-Hub-Signature-256 da Meta na edge, armazena o evento e depois assina de novo a entrega para o seu handler.

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

Perguntas que as equipes fazem

A Meta diz que minha URL de callback não pôde ser validada. Por quê?
Essa verificação é um GET com hub.mode=subscribe e hub.challenge, e ela falha a menos que sua URL devolva o challenge como o corpo inteiro, sem envelope JSON e sem quebra de linha no final. O AnyHook responde a ela, então uma URL de entrada é salva na primeira tentativa. Se você definir um Verify Token no AnyHook, digite a mesma string no App Dashboard.
Instagram, Facebook e Threads precisam de três configurações separadas?
Não. Eles seguem um único contrato de webhook da Graph API, com a mesma assinatura X-Hub-Signature-256, então um único app do AnyHook (uma URL de entrada para uma origem) cobre os três. Escolha as inscrições que você quer no App Dashboard.
O que acontece com comentários e DMs durante o redeploy do meu bot?
O AnyHook armazena o evento assim que ele chega e tenta de novo no seu endpoint com backoff, então um deploy ou um crash não faz você perder a mensagem. A Meta vê uma confirmação de recebimento e para de se preocupar com isso.
O Verify Token é o mesmo que o App Secret?
Não, e eles não devem ser a mesma string. O App Secret assina cada payload e nunca viaja numa URL. O Verify Token é uma string que você inventa: ele chega na query string do handshake de configuração e só é usado nesse momento. Colocar seu App Secret numa query string o espalharia por todos os logs no caminho.

Troque uma URL. Mantenha seu handler de Instagram.

O plano Free cobre 10 mil eventos / mês. Sem SDK: aponte os webhooks de Instagram para sua URL de entrada do AnyHook.