Documentação do AnyHook

Um relay de webhooks (um serviço que recebe, guarda e encaminha seus webhooks). Aponte seu remetente para o AnyHook e ganhe novas tentativas (retries) automáticas, um log com todos os eventos, verificação de assinatura unificada e replay (um novo envio de um evento armazenado, feito por você) com um clique. Nenhum SDK necessário.

Início rápido

O AnyHook não precisa de SDK nem de pacote: você troca a URL do webhook no painel do remetente, e seu handler continua recebendo as mesmas requisições. Um ponto para planejar: novas tentativas e replays levam o timestamp original do remetente, então, onde a assinatura do remetente expira (Stripe, Slack e os demais com janela de tolerância em /providers), verifique o cabeçalho AnyHook-Signature para aceitá-los.

Caminho mais rápido (sem conta): rode curl -X POST https://anyhook.net/api/v1/quickstart para receber na hora uma URL de entrada ativa e uma chave de API, ideal para agentes de IA e testes rápidos. O endpoint expira em 7 dias se você não o vincular à sua conta pela claim_url devolvida. Aponte seu remetente para a inbound_url devolvida e leia os eventos com GET /api/v1/events e GET /api/v1/events/{id}; o campo next_steps da resposta traz cada comando.

  1. 1
    Crie uma conta
    Acesse anyhook.net/sign-up e crie uma conta gratuita. Sem cartão de crédito.
  2. 2
    Crie um app
    Um app representa uma origem de webhooks. Uma conta nova abre direto no formulário: dê um nome ao app (por exemplo, "Stripe Production") e, se quiser, a URL do seu servidor, para onde o AnyHook encaminha cada evento depois de recebê-lo. Deixe em branco e o app registra tudo no log, sem encaminhar. Adicione destinos quando quiser, até 3 por app no Free.
  3. 3
    Aponte seu remetente para o AnyHook
    A página do app mostra sua URL de entrada e onde cada remetente comum pede por ela (por exemplo, Stripe → Developers → Webhooks). Cole a URL lá, no lugar da URL antiga do webhook:
    https://in.anyhook.net/{user-slug}/{app-slug}
    A página fica observando: a primeira requisição aparece nela em segundos. Ainda não tem de onde enviar? Clique em Send a test event na mesma página.

Formato da URL de entrada

Cada app ganha uma URL de entrada única:

https://in.anyhook.net/{user-slug}/{app-slug}
  • user-slug: o identificador da sua conta, visível nas configurações do painel.
  • app-slug: único por app, gerado a partir do nome do app, mas editável.

O endpoint de entrada aceita qualquer requisição POST e também responde aos handshakes de validação da configuração: um GET com hub.mode=subscribe (a verificação hub.challenge da Meta) recebe o desafio de volta automaticamente, mesmo antes de o app ser configurado. Qualquer outro método recebe 405. Páginas web e formulários HTML simples também podem enviar para ele (veja Handlers de código). O AnyHook responde HTTP 200 assim que o evento está gravado na fila durável, em cerca de um segundo, seja qual for a velocidade do seu endpoint de destino, então o remetente nunca espera por ele.

Tipos de conteúdo. O AnyHook guarda um corpo de texto, como application/json, application/x-www-form-urlencoded, XML ou texto puro, byte a byte e o encaminha com o Content-Type original. O AnyHook armazena os corpos como texto, então um payload binário, ou um arquivo em um formulário multipart, não chega intacto.

Origens compatíveis

O AnyHook detecta o remetente automaticamente pelos cabeçalhos recebidos e verifica a assinatura do payload na edge (o Cloudflare Worker em in.anyhook.net). Para ativar a verificação, selecione seu provedor de webhooks ao criar um app e cole o segredo de assinatura do webhook do provedor (por exemplo, o whsec_... da Stripe).

Altamente recomendado. Sem um segredo de assinatura, o AnyHook não consegue verificar se as requisições recebidas vieram mesmo do seu provedor. As requisições não verificadas continuam sendo encaminhadas; com o segredo, o AnyHook rejeita requisições forjadas na edge, antes que entrem no seu pipeline.
OrigemCabeçalho de assinaturaAlgoritmo
StripeStripe-SignatureHMAC-SHA256 + timestamp
GitHubX-Hub-Signature-256HMAC-SHA256
Meta (Instagram / Facebook / Threads)X-Hub-Signature-256HMAC-SHA256
ShopifyX-Shopify-Hmac-Sha256HMAC-SHA256 (Base64)
DiscordX-Signature-Ed25519Ed25519
SlackX-Slack-SignatureHMAC-SHA256 + timestamp
LINEx-line-signatureHMAC-SHA256 (Base64)
Xerox-xero-signatureHMAC-SHA256 (Base64)
LinearLinear-SignatureHMAC-SHA256
SentrySentry-Hook-SignatureHMAC-SHA256
Vercelx-vercel-signatureHMAC-SHA1
PaddlePaddle-SignatureHMAC-SHA256 + timestamp
SendGridX-Twilio-Email-Event-Webhook-SignatureECDSA P-256
TwilioX-Twilio-SignatureHMAC-SHA1
IntercomX-Hub-SignatureHMAC-SHA1
HubSpotX-HubSpot-Signature-v3HMAC-SHA256 (v3) / SHA-256 (v1/v2)
Standard Webhooks (Svix, OpenAI, Replicate)webhook-signature / Svix-SignatureHMAC-SHA256 + timestamp
ClerkSvix-SignatureHMAC-SHA256 (via Svix)
ResendSvix-SignatureHMAC-SHA256 (via Svix)
Lemon SqueezyX-SignatureHMAC-SHA256
WooCommerceX-WC-Webhook-SignatureHMAC-SHA256 (Base64)
PayPalPAYPAL-TRANSMISSION-SIGRSA-SHA256
Generic / personalizadaNenhumSem verificação

A edge rejeita as requisições que falham na verificação, e elas nunca chegam ao seu endpoint. A página do app conta essas requisições, e as 5 primeiras de cada dia ficam guardadas no log de eventos como rejected, com cabeçalhos e corpo, para você ver o motivo.

Meta: Instagram, Facebook, Threads

Os três seguem um só contrato de webhook da Graph API, então um único app cobre todos. A Meta assina com X-Hub-Signature-256, o mesmo cabeçalho que o GitHub usa; o AnyHook distingue os dois pelo cabeçalho X-Github-Event, que o GitHub sempre envia e a Meta nunca envia.

A Meta não salva uma URL de callback até que essa URL responda a um GET com hub.mode=subscribe devolvendo o hub.challenge como corpo inteiro da resposta. O AnyHook responde por você, então a URL de entrada é salva na primeira tentativa.

No AnyHookNo Meta App Dashboard
Source: MetaApp settings → Basic
App SecretCopie de lá para o AnyHook
Verify Token (opcional)Webhooks → Verify token: digite a mesma string
Sua URL de entradaWebhooks → Callback URL: cole a URL
O Verify Token vai no sentido contrário. Todos os outros segredos aqui são copiados do provedor. Este você inventa e depois digita na Meta. Ele não é o App Secret e não deve ser a mesma string: ele chega na query string do handshake de configuração, e uma chave de assinatura não deve ficar em uma URL. Deixe em branco e a inscrição se completa mesmo assim; defina um e o AnyHook o confere antes de responder.

A Meta tenta de novo uma entrega com falha, com frequência cada vez menor, por 36 horas e depois a descarta. Como o AnyHook armazena o evento antes de tentar seu endpoint, um deploy ou uma queda do seu lado não consome essa janela.

Como a entrega funciona

O AnyHook usa uma arquitetura assíncrona, baseada em fila, para desacoplar o remetente dos webhooks do seu backend:

  1. Recebimento na edge: o Cloudflare Workers aceita o POST, verifica a assinatura e responde 200 assim que o evento está armazenado de forma durável, em cerca de um segundo.
  2. Fila: a edge publica o evento em uma fila de mensagens durável (Upstash QStash). A conexão HTTP do remetente já está fechada.
  3. Encaminhamento: o QStash entrega o evento ao forwarder do AnyHook, que faz um POST do payload original para o seu endpoint de destino.
  4. Log: o AnyHook registra cada etapa, com cabeçalhos/corpo de entrada, status da entrega, código e corpo da resposta do seu endpoint e latência.
Por que isso importa. Seu remetente (Stripe, GitHub etc.) recebe o 200 em cerca de um segundo. Depois disso, seu backend pode levar até o timeout do seu plano para responder (60 s no Free, 120 s no Pro, 5 minutos no Scale), e o remetente não tenta de novo, porque já recebeu o 200. Esperar além do primeiro segundo usa sua franquia de computação.
Limite de tamanho do payload. Payload máximo por evento: 512 KB (Free), 2 MB (Pro), 4 MB (Scale), sob medida (Enterprise). A edge rejeita requisições grandes demais com 413 Payload Too Large: nunca são descartadas em silêncio, nunca são cobradas. A página do app conta essas requisições.
Modelo de entrega. Por padrão, o AnyHook encaminha os eventos no ritmo em que chegam, sem atraso e sem limite de concorrência. A partir do Pro, um destino pode definir um rate limit por segundo e um atraso na primeira tentativa. Se seu endpoint responder 429, o AnyHook espera e tenta de novo.

Garantias de entrega

O contrato, dito com todas as letras, porque um relay vago sobre as próprias garantias empurra essa imprecisão para o seu código:

  1. Pelo menos uma vez (at-least-once), não exatamente uma vez. As novas tentativas são o produto, e uma nova tentativa depois de uma resposta perdida não se distingue de uma entrega nova. De vez em quando, você vai receber o mesmo evento duas vezes; nenhum relay pode prometer o contrário, e os que prometem estão descrevendo o caminho feliz. Seu handler precisa ser idempotente, e o id do evento que encaminhamos em X-AnyHook-Delivery é a chave para deduplicar.
  2. Sem garantia de ordem. Com novas tentativas e entregas simultâneas, um evento posterior pode chegar antes de um anterior que o AnyHook ainda está tentando de novo. A maioria dos remetentes também não promete ordem antes de os eventos chegarem até nós. Ordene por um campo dentro do payload (uma versão, um updated_at), nunca pela hora de chegada.
  3. Não deduplique pela assinatura. O cabeçalho AnyHook-Signature é assinado de novo com um timestamp novo a cada tentativa, de propósito, para que as novas tentativas nunca falhem na sua janela de tolerância. Duas entregas do mesmo evento levam assinaturas diferentes; o que fica constante é o id do evento.
  4. Aceito significa durável. A edge só responde 200 depois que o evento está gravado, e, atrás da fila, há dois armazenamentos de fallback antes que a edge chegue a responder 503. Um 503 da URL de entrada significa que nada foi armazenado, e a nova tentativa do próprio remetente é a rede de segurança; é por isso que respondemos 503 em vez de fingir.
A versão prática. Escreva o handler de modo que processar o mesmo evento duas vezes não cause dano, e trate a ordem de chegada como ruído. Reunimos os padrões, ordenados por quanto protegem você, em Você recebeu o mesmo webhook duas vezes. É o contrato, não um bug.

Cronograma de novas tentativas

Quando seu destino responde com um erro temporário ou dá timeout, o AnyHook tenta de novo automaticamente, com backoff exponencial:

TentativaIntervaloPlano
1ImediataTodos
25 minutos depoisTodos
330 minutos depoisTodos
43 horas depoisPro, Scale, Enterprise
518 horas depoisPro, Scale, Enterprise
6-1024 horas depois, cada umaScale, Enterprise
Timeout de entrega. O AnyHook espera até 60 s (Free), 120 s (Pro) ou 5 minutos (Scale+) pela resposta do seu endpoint. Se seu handler precisar de ainda mais tempo, por exemplo, um agente de IA com várias etapas, responda HTTP 202 Accepted imediatamente e processe de forma assíncrona. O AnyHook marca a entrega como bem-sucedida no 202.

Quais respostas disparam novas tentativas?

Nem todo erro é temporário. O AnyHook classifica a resposta do seu endpoint para não gastar tentativas, nem a sua franquia de computação, com erros que tentar de novo não resolve:

Código de statusAçãoPor quê
2xxSucesso: entrega concluídaSeu endpoint aceitou o evento.
3xxDefinitivo: marcado como falha na horaO AnyHook não segue redirecionamentos; aponte o destino para a URL final.
408 Request TimeoutNova tentativa com backoffTimeout temporário, pode dar certo na próxima tentativa.
429 Too Many RequestsNova tentativa com backoffSeu endpoint está aplicando rate limit; dê tempo a ele.
4xx (outros: 400, 401, 403, 404, 422…)Definitivo: marcado como falha na horaErro do cliente: a requisição está malformada ou não autorizada. Tentar de novo o mesmo payload não adianta.
5xxNova tentativa com backoffErro do servidor, provavelmente temporário.
Erro de rede / timeoutNova tentativa com backoffSeu endpoint estava inacessível.
Por que 4xx é definitivo. Um 400 Bad Request ou 422 Unprocessable Entity significa que seu handler rejeitou o payload, e tentar de novo não muda o resultado. Com a falha marcada na hora, você é avisado mais rápido, não gasta franquia de computação com novas tentativas e pode fazer o replay do evento depois de corrigir seu handler.

Cada tentativa fica registrada com o corpo completo da resposta e o código de status. Tanto as falhas definitivas 4xx quanto as temporárias contam para o circuit breaker: 20 falhas consecutivas de qualquer tipo abrem o circuit breaker.

Notificações de falha

O AnyHook envia alertas por e-mail em limiares específicos, e não a cada evento, para que sua caixa de entrada continue gerenciável:

  • 1ª falha: alerta imediato com o código de status e o destino.
  • 5ª falha consecutiva: alerta de escalonamento com sugestões.
  • 20ª falha consecutiva: o circuit breaker abre (veja abaixo) e o alerta do circuit breaker é enviado.

Um e-mail em cada limite acima, depois um em cada etapa do circuit breaker (cada pausa e a desativação), nunca um por evento. Uma única entrega bem-sucedida zera o contador.

Circuit breaker

Se o AnyHook detectar 20 falhas de entrega consecutivas para o mesmo destino (incluindo 4xx definitivos), o circuit breaker abre e começa um escalonamento progressivo:

AberturaDuração da pausaO que acontece
110 minutosDestino pausado, e-mail enviado
230 minutosDestino pausado, e-mail enviado
32 horasDestino pausado, e-mail enviado
424 horasDestino pausado, e-mail enviado
5PermanenteDestino desativado, e-mail enviado

Como funciona

  • A entrada continua recebendo. Sua URL de entrada fica ativa o tempo todo: os eventos da Stripe, do GitHub etc. continuam sendo aceitos e armazenados. Só a entrega de saída para o seu destino fica pausada.
  • Quando cada pausa termina, o AnyHook faz um teste half-open: o próximo evento testa seu destino. Se der certo, o circuito fecha e tudo é zerado.
  • Um único sucesso zera tudo: a contagem de falhas consecutivas e a de aberturas voltam a zero.
  • Os eventos recebidos durante uma pausa ficam armazenados com status circuit_open e ficam disponíveis para replay assim que seu destino se recuperar.
  • Você recebe uma notificação por e-mail a cada etapa do escalonamento, com a URL do destino, a contagem de falhas e a duração da pausa.
  • Se o destino for desativado (abertura 5), você precisa reativá-lo manualmente pelo painel.

Essa abordagem progressiva dá tempo para seu servidor se recuperar e poupa sua franquia de computação das novas tentativas que um endpoint fora do ar consumiria.

Log de eventos

Todo evento que chega à sua URL de entrada fica registrado, aconteça o que acontecer com a entrega. O log começa quando você aponta o provedor para o AnyHook: um evento que falhou antes disso só existe no provedor (para a Stripe, veja como fazer o replay de um webhook). Cada entrada do log inclui:

  • Cabeçalhos e corpo de entrada completos (como o remetente enviou)
  • Status da entrega: queued → success / retrying / failed / rejected / circuit_open / quota_exceeded. rejected significa que o AnyHook recusou o evento (a verificação de assinatura falhou ou a URL de destino estava bloqueada); ele nunca foi tentado e não pode passar por replay.
  • O código e o corpo da resposta HTTP do seu destino (por tentativa)
  • Latência em milissegundos
  • Número de tentativas e horário da próxima tentativa
  • Se o evento é um replay (is_replay: true)

Quanto tempo os eventos ficam guardados depende do plano. Free: 3 dias, disponíveis para replay o tempo todo e depois excluídos. Pro: 1 ano: disponíveis para replay por 30 dias e depois em arquivamento criptografado, somente leitura. Scale: 7 anos: disponíveis para replay por 90 dias e depois em arquivamento criptografado, somente leitura. Enterprise: flexível.

Um app pausado não guarda nada. Enquanto um app está pausado (is_active: false), a URL de entrada dele responde 202 e não armazena nada: o provedor não desativa o endpoint, mas os eventos se perdem. Tire o app da pausa antes que chegue o tráfego de que você precisa.

Replay pelo painel

Você pode fazer o replay de qualquer evento do log pelo painel, com um clique. O replay envia de novo o payload armazenado, byte a byte, ao destino para onde o evento foi, ou à nova URL desse destino, se você a mudou depois.

  • Acesse Dashboard → Events
  • Encontre o evento e clique no botão Replay
  • O AnyHook cria um novo evento com is_replay: true, vinculado ao original
Cota. Os replays não consomem sua cota mensal de eventos, exceto o replay de um evento retido (quota_exceeded), que conta uma vez, como um evento novo. Um replay para o código de um app conta como uma execução de código.

Replay pela API (um evento)

POST/api/v1/events/{id}/replayReplay de um único evento, pelo ID

Publica o payload original do evento no QStash para uma nova entrega. O novo evento fica vinculado ao original pelo campo replay_of.

curl -X POST https://anyhook.net/api/v1/events/{event_id}/replay \
  -H "Authorization: Bearer {api_key}"

Responde 200 com o ID do novo evento quando dá certo. Responde 410 Gone se o evento já passou da janela de replay.

Eventos não entregues

GET/api/v1/apps/{slug}/events/undeliveredLista os eventos cuja entrega falhou

Devolve os eventos com status failed, circuit_open ou quota_exceeded.

ParâmetroTipoPadrãoDescrição
sinceISO 8601há 24 hSó eventos criados depois deste timestamp
limitinteger100Máximo de eventos a devolver (1-200)
curl "https://anyhook.net/api/v1/apps/stripe-prod/events/undelivered?since=2026-04-11T00:00:00Z&limit=50" \
  -H "Authorization: Bearer {api_key}"
{
  "count": 3,
  "events": [
    { "id": "evt_abc", "status": "failed", "created_at": "..." },
    ...
  ]
}

Replay em lote

POST/api/v1/apps/{slug}/replay-failedReplay dos eventos com falha de um app

Faz o replay dos eventos com falha que atendem aos critérios, começando pelos mais antigos, até o limite por chamada. Os eventos que já têm um replay, ou que passaram da janela de replay, ficam de fora.

Parâmetro do corpoTipoPadrãoDescrição
sinceISO 8601há 24 hSó faz o replay dos eventos criados depois deste timestamp
limitinteger50Máximo de eventos para o replay (1-100)
curl -X POST https://anyhook.net/api/v1/apps/stripe-prod/replay-failed \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{"since": "2026-04-11T00:00:00Z", "limit": 100}'
{
  "replayed": 3,
  "events": [
    { "original_id": "evt_abc", "new_id": "evt_xyz" },
    ...
  ]
}
Limite diário de replays. Free: 100/dia, Pro: 500/dia, Scale: 1.000/dia, Enterprise: ilimitado.

Desenvolvimento local

O AnyHook funciona com desenvolvimento local sem configuração extra, sem modo especial nem CLI. Duas abordagens:

1. Encaminhamento ao vivo por um túnel

Defina como destino do app uma URL de túnel que aponte para a sua máquina local. Qualquer provedor de túnel serve:

# ngrok
ngrok http 3000
# → https://abc123.ngrok.io

# cloudflared
cloudflared tunnel --url http://localhost:3000

# localhost.run
ssh -R 80:localhost:3000 nokey@localhost.run

Copie a URL do túnel para o destino do app no painel. O AnyHook encaminha os webhooks para lá exatamente como para qualquer outro endpoint: novas tentativas, log e cabeçalhos de assinatura funcionam do mesmo jeito.

Dica. Mantenha seu provedor apontado para a sua URL de entrada de produção (https://in.anyhook.net/you/app) enquanto desenvolve. Você ganha eventos reais capturados no seu log e pode alternar o destino entre o túnel e a produção sem mexer na Stripe.

2. Capture e faça o replay para o localhost

Deixe os eventos reais de produção se acumularem no AnyHook e faça o replay de qualquer um deles para o seu ambiente local quando precisar. Não é preciso disparar eventos de teste à mão: teste com payloads reais, quantas vezes o seu limite diário de replays permitir.

  • Troque o destino pela URL do seu túnel
  • Abra Dashboard → Events e clique no botão Replay de qualquer evento
  • Ou use a API: POST /api/v1/events/{id}/replay
  • O AnyHook envia o payload original, byte a byte, ao seu destino atual

Isso é especialmente útil para depurar casos extremos: encontre o evento problemático nos logs de produção, faça o replay dele localmente e repita até seu handler funcionar direito.

Os replays não consomem cota. Os replays não consomem sua cota mensal de eventos. Eles usam a franquia de computação e um limite diário de replays (100 no Free, 500 no Pro, 1.000 no Scale).

Criptografia em repouso

O AnyHook criptografa em repouso todos os dados de payload dos webhooks com AES-256-GCM antes que cheguem ao banco de dados. Isso inclui cabeçalhos de entrada, corpos de requisição e os segredos de assinatura de cada destino.

A criptografia usa uma chave dedicada (AES_SECRET_KEY), armazenada separadamente das credenciais do banco de dados. Um vazamento do banco sem a chave de criptografia só entrega texto cifrado: nenhum dado de webhook em texto puro, nenhum segredo de assinatura, nenhuma informação de cliente.

O que é criptografado

  • Cabeçalhos e corpo das requisições de webhook (todo evento)
  • Segredos de assinatura dos provedores (o segredo do webhook da Stripe etc.)
  • Segredos de assinatura do AnyHook de cada destino
Nenhum segredo em texto puro no banco de dados. O AnyHook criptografa com AES-256-GCM cada segredo e cada payload de webhook antes de armazená-los. Mesmo com acesso total ao banco de dados, um invasor só vê texto cifrado.

Verificação na edge

O AnyHook verifica as assinaturas dos webhooks na edge da Cloudflare, antes que o evento entre na fila. Quando um app tem seu segredo de assinatura, uma requisição com assinatura ausente ou inválida é recusada com um 401 e nunca consome sua cota; sem um segredo, as requisições são encaminhadas sem verificação.

A detecção da assinatura é automática, pelos cabeçalhos. Quando você cria um app e seleciona um tipo de origem, o AnyHook armazena o segredo de assinatura (criptografado em repouso) e usa o algoritmo certo para aquele provedor.

Requisições inválidas. A edge rejeita com HTTP 401 as requisições que falham na verificação de assinatura, e elas nunca chegam ao seu destino. A página do app conta essas requisições, e as 5 primeiras de cada dia ficam guardadas no log de eventos, com cabeçalhos e corpo.

Origens não compatíveis ou personalizadas

Se a origem dos seus webhooks não está na lista de compatíveis, selecione Generic ao criar o app. O AnyHook vai:

  • Aceitar e colocar na fila todo POST recebido, sem verificação de assinatura na entrada.
  • Encaminhar ao seu destino o corpo byte a byte e os cabeçalhos originais do provedor, mais um cabeçalho AnyHook-Signature novo a cada tentativa. Removemos os cabeçalhos de infraestrutura adicionados antes da nossa edge (cf-* e afins).
  • Anexar um cabeçalho AnyHook-Signature para que seu endpoint ainda possa verificar se a requisição passou pelo AnyHook.

Um app Generic não tem verificação de assinatura na entrada, seja qual for o segredo definido. O cabeçalho AnyHook-Signature prova que uma requisição passou pelo AnyHook, não quem a enviou ao AnyHook; se você precisa disso, mantenha sua própria verificação do cabeçalho original do remetente (encaminhado intacto). Se o remetente assina com o padrão Standard Webhooks (webhook-id, webhook-timestamp, webhook-signature, como fazem a OpenAI e a Replicate), escolha essa origem: ela é verificada na edge.

AnyHook-Signature

Toda requisição encaminhada inclui um cabeçalho AnyHook-Signature, uma assinatura HMAC-SHA256 com timestamp, gerada com o segredo de assinatura do seu destino.

AnyHook-Signature: t=1712934567,v1=5257a869...

Para verificar no seu servidor:

// Node.js / TypeScript
import crypto from "crypto";

function verifyAnyHook(header: string, body: string, secret: string): boolean {
  const [tPart, vPart] = header.split(",");
  const timestamp = Number(tPart?.replace("t=", ""));
  // Re-signed on every attempt, so a genuine signature is never older than this.
  if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
  const signature = Buffer.from(vPart?.replace("v1=", "") ?? "", "hex");

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${body}`)
    .digest();

  // timingSafeEqual throws on a length mismatch, so check that first.
  return signature.length === expected.length && crypto.timingSafeEqual(signature, expected);
}

O AnyHook gera um segredo de assinatura para cada destino, e ele fica disponível no painel em App → Destinations → Signing secret. Você pode rotacioná-lo a qualquer momento pela API.

Cabeçalhos personalizados

Você pode anexar cabeçalhos personalizados às requisições de saída, destino por destino. Casos de uso comuns:

  • Authorization: Bearer ...: autenticação na sua API
  • x-api-key: ...: autenticação por chave de API
  • Qualquer outro cabeçalho que seu endpoint exija

O AnyHook criptografa em repouso os valores dos cabeçalhos personalizados e nunca os expõe no log de eventos. Configure-os em App → Destinations → Custom headers.

Cabeçalhos originais preservados. O AnyHook encaminha byte a byte os cabeçalhos originais do provedor (Stripe-Signature etc.), junto com seus cabeçalhos personalizados e o AnyHook-Signature, então a verificação que você já faz com o SDK passa na primeira entrega. As novas tentativas e os replays levam o timestamp original do provedor, que o SDK da Stripe rejeita depois de 5 minutos, por padrão: nesses casos, verifique o AnyHook-Signature, que é assinado de novo a cada tentativa.

Controles de entrega

Três configurações opcionais em cada destino, todas no Pro e acima. Defina-as em App → Destinations → Delivery controls ou com PATCH /api/v1/apps/{slug} no objeto do destino. Uma conta Free mantém os controles que definiu enquanto pagava, mas não pode alterá-los.

{
  "destinations": [{
    "url": "https://api.example.com/hooks/stripe",
    "event_types": ["invoice.*", "checkout.session.completed"],
    "rate_limit": 20,
    "delay_seconds": 5
  }]
}

Filtros por tipo de evento

event_types é uma lista de padrões; o destino só recebe os eventos cujo tipo corresponde a um deles. * corresponde a qualquer sequência de caracteres, então invoice.* são todos os eventos de fatura e checkout.session.completed é exatamente um. A comparação ignora maiúsculas e minúsculas e vale para o tipo inteiro. Com dois destinos, você pode separar por tipo: eventos de cobrança para um serviço, todo o resto para outro.

  • Decidido na edge, depois da verificação de assinatura e antes do contador da cota: um evento filtrado não é armazenado, não é entregue e não conta.
  • O remetente recebe 200 {"filtered":true,"event_type":"…"}, que a Stripe e a Shopify mostram nos próprios logs de webhooks.
  • De onde vem o tipo depende do provedor: um cabeçalho para GitHub, Shopify, WooCommerce e Lemon Squeezy; o type do corpo para Stripe, Clerk, Resend, Vercel e remetentes no estilo Svix; event_type para Paddle e PayPal. É o mesmo valor que o log de eventos mostra na coluna Type.
Tipos desconhecidos são sempre entregues. Um evento cujo tipo o AnyHook não consegue ler (um corpo genérico sem o campo type, um ping do provedor) vai para todos os destinos, independentemente dos filtros. A promessa do relay é que nada se perde; um filtro que engolisse o desconhecido quebraria essa promessa.

Rate limit

rate_limit limita as entregas a um destino a esse número por segundo (de 1 a 1.000). Os eventos acima do limite aguardam na fila e saem em ordem conforme a capacidade se libera; nada é descartado, e as novas tentativas contam para o mesmo limite. Use quando um remetente pode mandar rajadas mais rápido do que seu servidor consegue absorver: uma atualização de produtos em massa na Shopify, uma onda de renovações de assinaturas da Stripe no início do mês, um push num monorepo do GitHub que se desdobra em muitos eventos de CI.

O replay em lote também respeita o limite, e é justamente aí que ele mais importa: fazer o replay de 500 eventos com falha a 20 por segundo dá 25 segundos de tráfego constante, em vez de um pico que falha de novo.

Atraso de entrega

delay_seconds adia a primeira tentativa de entrega (de 1 segundo a 1 dia). O motivo de costume é um remetente cujo webhook corre contra a própria API: o checkout.session.completed da Stripe pode chegar antes que sua página de sucesso termine de gravar o pedido, e um atraso de cinco segundos faz essa condição de corrida deixar de existir. Os replays nunca sofrem atraso; o cronograma de novas tentativas depois da primeira tentativa não muda.

Alertas no Slack / Discord

No Pro e acima, todo alerta que o AnyHook envia por e-mail também é postado num incoming webhook que você configura uma vez em Settings → Notifications: falhas de entrega (1ª e 5ª consecutivas), aberturas e desativações do circuit breaker, ondas de rejeições e sua resolução e os avisos de cota em 50% / 90% / 100%. Ao salvar a URL, o AnyHook posta uma mensagem de teste para você saber que ela chegou. Slack, Discord, Mattermost, Rocket.Chat e o conector de incoming webhook do Teams funcionam; o payload é {"text": …} (ou content no Discord). Desligar "Email on failure" silencia só o e-mail; o canal continua recebendo os alertas.

Handlers de código

No Pro e no Scale, um app pode rodar seu próprio código a cada evento que recebe, num sandbox, sem um servidor seu. Ao salvar o código, o app ganha um destino que o executa, então as novas tentativas, o replay, o circuit breaker e o log de eventos funcionam como para qualquer destino, e a saída do console e o valor de retorno de cada execução são a resposta daquela entrega. Escreva o código na seção Code do app ou com os endpoints abaixo. Há modelos para ações comuns em /code-templates.md, e a migração do Pipedream tem um guia próprio.

export default async function (event, { env, store }) {
  // event: { method, headers, body, raw_body, delivery_id, attempt }
  console.log(event.body);
}
  • O evento. body é o JSON já interpretado, os campos de formulário (urlencoded, ou os campos de texto de um formulário multipart) ou o texto bruto, e raw_body é o texto como chegou. method é sempre POST; a query string, o caminho e o IP de quem chamou não ficam guardados. delivery_id é o id X-AnyHook-Delivery do evento, e attempt conta a partir de 1, com 0 numa execução de teste.
  • Falhas. Lançar uma exceção faz a entrega falhar, e o AnyHook tenta de novo dentro das tentativas do plano (Cronograma de novas tentativas). Uma nova tentativa de um evento cuja execução deu certo não roda o código de novo, mas, se a execução falhou depois que uma chamada já tinha sido feita, a nova tentativa roda o código de novo, então use o delivery_id como chave de idempotência dessas chamadas, onde a API permitir.
  • Steps do Pipedream. export default defineComponent({ async run({ steps, $ }) {} }) também roda: steps.trigger.event é o evento acima, $.export e $.flow.exit funcionam, toda prop data_store é o store do app (um armazenamento chave-valor), o $auth.field de uma prop de app lê o segredo APP_FIELD, e qualquer outra prop lê o segredo com o nome dela (channelId lê CHANNEL_ID). O guia do Pipedream lista o que não é aproveitado.
  • Pacotes npm. O AnyHook instala o que o código importa quando você salva, com a versão fixada do jeito do Pipedream (import axios from "axios@1.7.9"), até 20 pacotes. Um salvamento que instala pacotes novos pode levar alguns minutos; uma instalação que falha não salva nada.
  • Segredos. Lidos como env.NAME ou process.env.NAME, até 20, com nomes de letras, dígitos e sublinhados, sem começar com dígito. PATH, HOME, NODE_OPTIONS, NODE_PATH, PWD e os nomes que começam com ANYHOOK_ pertencem ao runtime, e um valor tem no máximo 4.000 caracteres. Os valores nunca saem do servidor.
  • O store. store.get, set (com { ttl } em segundos), has, delete, keys, entries e clear mantêm o estado entre eventos, um store por app: até 1.000 chaves de até 256 caracteres, cada valor com no máximo 64.000 bytes em JSON. Uma importação recebe {"entries": {key: value}}, sobrescreve as chaves que já existem e as grava sem TTL.
  • Agendamentos. schedule são cinco campos de cron, em UTC, a não ser que venham prefixados com CRON_TZ=Area/City. Cada disparo posta um evento anyhook.schedule na própria URL do app, então conta como uma execução e como um evento, e só dispara enquanto o código está ligado. A origem do app precisa ser generic ou auto, sem segredo de assinatura: dê ao código agendado um app só para ele.
  • E-mail. Todo app também recebe e-mails em {user-slug}.{app-slug}@anyhook.net, como um evento email.received cujo corpo traz from, to, subject, message_id, date, size e a mensagem MIME como raw. Como um agendamento, precisa de um app cuja origem seja generic ou auto, sem segredo de assinatura, e um e-mail acima do limite de payload do plano é devolvido.
  • Páginas web e formulários. Uma página pode chamar a URL de entrada com fetch(): a edge responde ao preflight de CORS, e toda resposta leva Access-Control-Allow-Origin: *. Um formulário HTML simples precisa de method="post". Adicione ?redirect=https://your-site.com/thanks ao action dele e, quando o evento é armazenado, o visitante é levado para lá com um 303, desde que esse endereço esteja no site de onde o formulário foi enviado; caso contrário, o visitante vê uma página curta "Received". Os campos de texto chegam em event.body; os arquivos enviados se perdem.
  • Limites. O código tem no máximo 64.000 bytes, e uma execução pode durar 105 s no Pro e 285 s no Scale. Uma execução guarda os últimos 16.000 caracteres da saída do console. As execuções do mês estão na tabela de planos, e toda execução conta, incluindo novas tentativas, execuções de teste, replays e disparos de agendamento. Não há excedente: passadas as execuções do mês, uma entrega ao código recebe 429 e fica retida (circuit_open), sem novas tentativas, pronta para o replay quando o mês virar ou o plano aumentar.
  • Testes, ligar e desligar. POST .../code/test roda o código salvo no último evento armazenado do app, num event_id ou num body que você passar, e responde com os logs, o valor de retorno e a duração. Conta como uma execução e não entrega nada, mas o que o próprio código enviar acontece de fato. Os eventos só rodam o código enquanto ele está ligado. O painel salva código novo desligado, enquanto PUT .../code salva uma primeira versão já ativa, a não ser que você passe "enabled": false; um salvamento posterior mantém o estado atual. Enquanto outro destino ainda processa os mesmos eventos, como durante uma migração, salve com "enabled": false e ligue o código depois de remover esse destino. Qualquer uma das mudanças leva até um minuto para chegar a todos os locais da edge.
GET/api/v1/apps/{slug}/codeO código, os nomes dos segredos, o agendamento e os pacotes, se os eventos chegam até ele e as execuções usadas no mês
PUT/api/v1/apps/{slug}/codeSalva o código, os segredos (mesclados; null remove um), o agendamento (null o remove) e enabled
DELETE/api/v1/apps/{slug}/codeRemove o código, os segredos e o agendamento
POST/api/v1/apps/{slug}/code/testRoda o código salvo agora, num evento armazenado ou num body que você passar
GET/api/v1/apps/{slug}/storeTodas as chaves ativas do store do app
POST/api/v1/apps/{slug}/storeImporta chaves: {"entries": {key: value}}
DELETE/api/v1/apps/{slug}/storeRemove uma chave com ?key=, ou todas as chaves

Autenticação

Toda requisição à API exige um token Bearer no cabeçalho Authorization:

Authorization: Bearer ahk_live_xxxxxxxxxxxxxxxx

Gere chaves de API em Dashboard → Settings → API Keys. As chaves valem para a sua conta, dão acesso total ou somente leitura (você escolhe ao criar cada uma) e podem ser revogadas a qualquer momento. Um app conectado pelo login do MCP (anyhook.net/mcp) pode chamar os mesmos endpoints com o token de acesso OAuth dele, dentro do acesso que você aprovou.

Limites de API. As chamadas de API e MCP têm limite por conta e por dia UTC: 300 no Free, 1.000 no Pro, 3.000 no Scale. Passado o limite, você recebe 429 Too Many Requests com um cabeçalho Retry-After até 00:00 UTC.

Agentes de IA (MCP)

O AnyHook é um servidor MCP em https://anyhook.net/mcp, então um agente pode criar apps, ler o log de eventos e fazer o replay das falhas por você e, no Pro e no Scale, fazer deploy do código de um app e testá-lo (anyhook_code_deploy, anyhook_code_test, anyhook_code_get). Claude Code, claude.ai, Codex e VS Code se conectam com a URL e um login no navegador, sem chave para copiar: os passos para cada um.

O Cursor, o Windsurf e o Claude Desktop usam uma chave. Crie uma em Dashboard → Settings → API Keys, que mostra este trecho já com a sua chave:

{
  "mcpServers": {
    "anyhook": {
      "command": "npx",
      "args": [
        "-y",
        "anyhook-mcp"
      ],
      "env": {
        "ANYHOOK_API_KEY": "ahk_live_xxxxxxxxxxxxxxxx"
      }
    }
  }
}

Cursor: Settings → MCP → Add new MCP server, ou ~/.cursor/mcp.json. Windsurf: ~/.codeium/windsurf/mcp_config.json. Claude Desktop: Settings → Developer → Edit Config. As ferramentas de código precisam do anyhook-mcp 0.4.0 ou mais recente, que o npx -y busca, ou do servidor em https://anyhook.net/mcp.

Depois, peça: "Crie um app do AnyHook para os meus webhooks da Stripe e me passe a URL dele." Os agentes podem ler tudo isto em /llms.txt.

Apps

GET/api/v1/appsLista todos os apps
POST/api/v1/appsCria um app
GET/api/v1/apps/{slug}Mostra os detalhes de um app
PATCH/api/v1/apps/{slug}Atualiza um app
DELETE/api/v1/apps/{slug}Exclui um app

Crie um app:

curl -X POST https://anyhook.net/api/v1/apps \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe Production",
    "source": "stripe",
    "destination_url": "https://api.example.com/webhooks"
  }'
{
  "id": "app_xxxxx",
  "name": "Stripe Production",
  "slug": "stripe-production",
  "inbound_url": "https://in.anyhook.net/you/stripe-production",
  "destinations": [
    { "url": "https://api.example.com/webhooks", "enabled": true }
  ]
}

Conecte a Stripe ou o GitHub

POST/api/v1/connectRegistra a URL de um app no provedor, com uma chave do próprio provedor

Passe uma chave da Stripe ou um token do GitHub e o AnyHook adiciona a URL de entrada do app como webhook no provedor, armazena o segredo de assinatura que o provedor devolve e verifica as assinaturas desde o primeiro evento. O AnyHook identifica o provedor pela chave. A chave é usada nessas chamadas e não fica armazenada. Sem app, o AnyHook cria um app novo; rodar de novo substitui o cadastro anterior em vez de criar um segundo.

O privilégio mínimo basta: uma chave restrita da Stripe com Webhook Endpoints: Write ou um token fine-grained do GitHub com Webhooks: Read and write no repositório.

# Stripe: a restricted key, connecting an existing app
curl -X POST https://anyhook.net/api/v1/connect \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{ "key": "rk_live_...", "app": "payments" }'

# GitHub: name the repository (or an organization); no app, so one is created
curl -X POST https://anyhook.net/api/v1/connect \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{ "key": "github_pat_...", "target": "acme/api" }'

O GitHub envia um ping assinado assim que o hook existe, então o primeiro evento aparece em segundos. Outros provedores: crie o app com POST /api/v1/apps e cole a URL de entrada dele nas configurações de webhook do provedor.

Eventos

GET/api/v1/eventsLista resumos dos eventos, do mais recente ao mais antigo (sem payloads)
GET/api/v1/events/{id}Mostra os detalhes de um evento, com o payload completo

Parâmetros de consulta da listagem:

ParâmetroTipoDescrição
app_slugstringFiltra pelo slug do app
statusstringFiltra pelo status (queued, success, retrying, failed, rejected, circuit_open, quota_exceeded)
limitintegerMáximo de resultados (padrão 50, máximo 200)

Replay (API)

POST/api/v1/events/{id}/replayReplay de um único evento

Veja Replay pela API acima para detalhes completos e exemplos.

Não entregues (API)

GET/api/v1/apps/{slug}/events/undeliveredLista os eventos com falha/não entregues

Veja Eventos não entregues acima para detalhes completos e exemplos.

Replay em lote (API)

POST/api/v1/apps/{slug}/replay-failedReplay de todos os eventos com falha de um app

Veja Replay em lote acima para detalhes completos e exemplos.

Limites dos planos

FreePro ($20/mês)Scale ($90/mês)Enterprise
Eventos/mês10.00050.000250.000Flexível
Limite diário1.000NenhumNenhumNenhum
Apps11050Flexível
Destinos por app32050Flexível
Tentativas de entrega3510Flexível
Histórico3 dias1 ano7 anosFlexível
Disponível para replay por3 dias30 dias90 diasAté 365 dias
Timeout de entrega60 s120 s5 minFlexível
Payload máximo512 KB2 MB4 MBSob medida
ExcedenteNenhum$0.15/mil$0.17/milPor contrato
Limite diário de replays1005001.000Ilimitado
Execuções de código/mêsNenhum3.00012.000Flexível
Duração de uma execuçãoNenhum105 s285 sFlexível
Franquia de computação$0.15 por mês$3.00 por mês$11.00 por mêsFlexível
Chamadas de API e MCP300 por dia1.000 por dia3.000 por diaFlexível

Veja todos os detalhes de preços →

Uso e cotas

O AnyHook mede o uso por mês do calendário (por exemplo, "2026-04"). O contador é zerado no início de cada mês do calendário.

  • Conta uma vez por entrega: um evento enviado a dois destinos conta como duas entregas, e um evento grande conta uma vez a cada 64 KB iniciados de corpo e cabeçalhos. Um evento num app sem destino conta uma vez. Os eventos que um filtro descarta e os que falham na verificação de assinatura não contam. O limite diário do Free é mais simples: conta uma vez cada requisição que a edge aceita, para quantos destinos ela for.
  • Só a primeira tentativa: as novas tentativas não consomem sua cota.
  • Os replays não contam: os eventos com is_replay: true ficam fora da cobrança, exceto o replay de um evento retido além da sua cota, que conta uma vez, como um evento novo. Eles usam a franquia de computação e o limite diário de replays do seu plano.
  • Franquia de computação: o que o AnyHook gasta com a sua conta além da primeira tentativa de cada evento, a preço de custo: novas tentativas, replays, execuções de código, instalações de pacotes, disparos de agendamento, simulações de queda e a espera por um destino depois do primeiro segundo (uma nova tentativa de um evento de 1 KB usa cerca de $0.00002 dela, uma execução de código de poucos segundos, cerca de $0.0008). Passada a franquia, tudo isso para até o dia 1 (UTC): uma entrega com falha não é tentada de novo, replays e execuções de código são recusados, os agendamentos ficam pausados e um destino tem um segundo para responder. Os eventos continuam chegando e recebem a primeira tentativa.
  • Chamadas de API e MCP: cada chamada feita com uma chave de API ou por um cliente MCP conta uma, e uma resposta acima de 32 KB conta uma a cada 32 KB iniciados; o painel não conta. Passado o limite do dia, a API responde 429 com Retry-After até 00:00 UTC. GET /api/v1/usage (ou anyhook_usage pelo MCP) mostra cada limite e o que já foi usado dele.
  • Retidos, não descartados: além dos eventos incluídos (e além do seu teto de gastos, se você definiu um), cada evento fica registrado com status quota_exceeded e não é entregue. Faça upgrade ou defina um teto e depois faça o replay deles com um clique pelo painel. Os eventos retidos vão até 20% da cota além do que o teto compra; passado isso, a edge recusa com um 429 e não armazena nada, como limite para uma origem descontrolada.
Limite do plano Free. A edge recusa os eventos acima do limite diário (1.000). Os eventos acima da cota mensal (10.000) ficam retidos, até mais 20% dela: aparecem no seu log de eventos com status quota_exceeded e ficam disponíveis para replay depois do upgrade. Acima disso, a edge os recusa como no limite diário. Faça upgrade para o Pro para ter excedente medido.
Excedente nos planos pagos. O Pro cobra o excedente a $0.15/mil eventos, o Scale a $0.17/mil, arredondado para cima, ao centavo. Nunca descartamos em silêncio o tráfego pago.

Cobrança de excedente

Quando sua cota mensal acaba, a cobrança de excedente deixa os eventos continuarem fluindo em vez de ficarem retidos. Disponível só nos planos Pro e Scale.

Como funciona

  1. Defina um teto de gastos mensal em Settings ($5 / $10 / $20 / $50 ou Off).
  2. Quando a cota de eventos do plano é atingida, o AnyHook verifica se o excedente está ativado e se o teto ainda não se esgotou.
  3. Se for permitido, o AnyHook entrega o evento normalmente e o marca como excedente.
  4. No início do mês seguinte, o excedente é cobrado como um único item na sua fatura da Stripe: $0.15 por 1.000 eventos no Pro, $0.17 por 1.000 eventos no Scale (por evento, arredondado para cima ao centavo), até o seu teto de gastos.

Opção por app

Cada app tem uma configuração de excedente: Inherit default, Enabled ou Disabled. Por padrão, os apps herdam a configuração da conta. Mude app por app em App Settings → Overage Billing.

Padrão da conta

Em Settings → Overage Billing, um teto de gastos acima de Off liga o excedente em todos os apps definidos como Inherit default, inclusive os apps existentes; os apps definidos como Disabled continuam desligados.

O teto de gastos é um limite rígido. Quando o custo do excedente atinge o teto, os eventos seguintes não são entregues: cada um fica armazenado como quota_exceeded e disponível para replay assim que você aumentar o teto ou o mês recomeçar. Nada além do teto é cobrado. Além do teto, os eventos ficam retidos até mais 20% da cota; a edge recusa o resto com um 429 e não armazena nada.

Chaves de API

As chaves de API autenticam suas requisições à API REST do AnyHook. Gerencie-as em Dashboard → Settings → API Keys.

  • As chaves de produção começam com o prefixo ahk_live_.
  • Você pode criar várias chaves e dar um rótulo a cada uma (por exemplo, "CI/CD", "Agente").
  • Revogue uma chave na hora: uma chave revogada recebe 401 no próximo uso.
  • As chaves só aparecem uma vez, na criação. Guarde-as em um lugar seguro.
GET/api/v1/api-keysLista todas as chaves de API (com os segredos mascarados)
POST/api/v1/api-keysCria uma chave de API
PATCH/api/v1/api-keys/{id}/revokeRevoga uma chave de API

Precisa de ajuda?

Escreva para hello@anyhook.net: respondemos a todas as mensagens. Veja o estado do serviço em tempo real em status.anyhook.net.